Programming/JAVA

인터페이스란?

잇나우 2021. 2. 6. 22:28
반응형

인터페이스

자바는 하나의 상속만 가능하다는 특성이 있다. 이것은 객체지향 프로그래밍에서 큰 제약이기 때문에 인터페이스라는 개념을 도입햇다. 인터페이스는 일종의 추상 클래스이다. 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상 클래스와 달리 일반 메서드 또는 멤버변수를 가질 수 없다. 오직 추상메서드와 상수만 멤버로 가질 수 있다. 하지만 자바 8 부터 default 키워드를 통해 일반 메서드 구현이 가능해졌다.

개발 코드와 객체가 서로 통신하는 접점

개발 코드가 인터페이스의 메서드를 호출하면 인터페이스는 객체의 메서드를 호출한다. 이렇게 되면 개발코드는 객체의 내부 구조를 알 필요가 없고, 인터페이스의 메서드만 알면 된다는 장점이 있다.

  • 개발 코드를 수정하지 않고, 사용하는 객체를 변경할 수 있도록 하기 위해 인터페이스를 사용한다.
  • 인터페이스는 하나의 객체가 아니라, 여러 객체들과 사용이 가능하다. 어떤 구현 객체를 사용하느냐에 따라 실행 내용과 리턴값이 다를 수 있다. 코드 변경없이 실행 내용과 리턴값을 다양화 할 수 있다는 장점을 가진다.

인터페이스 정의

인터페이스는 .java 형태의 소스 파일로 작성이되고 컴파일러 javac.exe를 통해 .class 형태로 컴파일 되므로 물리적 형태는 클래스와 동일하지만 선언 방식이 다르다.

  • interface 키워드를 사용하여 선언한다.

      public interface 인터페이스명 {
          ...
      }
  • 인터페이스 이름은 클래스 이름 작성 방법과 동일하다.

  • class와 같이 접근제어자로 public 또는 default를 사용할 수 있다.

  • 인터페이스는 상수와 메서드만 멤버로 가진다.

  • 인터페이스는 객체를 생성할 수 없기 때문에 생성자를 가질 수 없다.

  • 자바 8부터는 default 메서드와 static 메서드도 선언이 가능하다.

    interface 인터페이스명 {
      // 상수
      (public) (static) (final) 타입 상수명 = 값;
    
      // 추상 메서드
      (public) (abstract) 타입 메서드명(매개변수);
    
      // 디폴트 메서드
      default 타입 메서드명(매개변수) {
          ...
      }
    
      // 정적 메서드
      static 타입 메서드명(매개변수) {
          ...
      }
    }

추상 클래스와 인터페이스 차이

  • 추상클래스
    추상 메서드를 포함하는 일반 클래스로 생성자, 인스턴스 변수 등을 멤버로 가질 수 있다.
abstract class Phone {
    String name; // 인스턴스 변수
    Phoner(){} // 생성자
    abstract void buy(); // 추상메서드
    void sell(){} // 인스턴스 메서드
}
  • 인터페이스
    추상 메서드만으로 이루어진 집합으로 자바 8부터는 인터페이스도 일반 메서드를 가질 수 있게 되었다.
interface Phone {
    public static final String type = "smartPhone"; // 상수
    void buy(int price); // 추상메서드
}

추상 클래스는 IS - A ~이다,
인터페이스는 HAS - A ~는 ~를 할 수 있다.

public abstract class Person {
    public int age;

    public abstract void language(); // 추상 메서드

    public void eat() {
        System.out.println("음식을 먹는다.");
    }

    public void walk() {
        System.out.println("걷는다.");
    }
}

public interface SwimAble {
    void swim();  // public abstract 생략 가능
}

public class Honggildong extends Person implements SwimAble {
    public Honggildong(int age) {
        this.age = age;
    }

    @Override
    public void language() {
        System.out.println("한국어를 사용합니다.");
    }

    @Override
    public void swim() {
        System.out.print("헤엄을 잘 칩니다.");
    }
}

인스턴스 구성 멤버

  1. 상수 필드 (Constant Field) 선언

    • 인터페이스는 데이터를 저장할 수 없기 때문에 데이터를 저장할 인스턴스 또는 static 필드를 선언할 수 없다. 상수 필드만 선언이 가능하다.

        (public static final) 타입 상수명 = 값;

      public, static, final은 컴파일 과정에서 자동으로 추가 되기 때문에 생략이 가능하다. static {} 블록으로 초기화할 수 없기 때문에 선언과 동시에 초기값을 지정해주어야 한다.

  2. 추상 메서드 선언

    • 인터페이스를 통해 호출된 메소드는 최종적으로 구현 객체에서 실행된다. 인터페이스의 메서드는 어차피 구현객체에서 실행되니까 추상 메서드로 선언한다.
    • 인터페이스에 선언된 추상 메서드는 모두 public abstract를 생략해도 자동으로 추가된다.

인터페이스 상속

자바는 클래스의 다중상속이 안된다. 부모들의 메서드가 자손에서 충돌나는 문제를 방지하기 위해서다. 하지만 인터페이스에 존재하는 추상 메서드는 선언부만 존재하기 때문에 충돌날 가능성이 없다. 따라서 다중상속이 가능하다. 인터페이스의 부모는 인터페이스만 가능하다.

interface Movable {
    void move(int x, int y);
}

interface Attackable {
    void attack(Unit u);
}

interface Fightable extends Movable, Attackable {}
// Smartphone 인터페이스는 자동으로 멤버를 2개 가지게 된다. (move, attack)

인터페이스 구현

인터페이스는 추상메서드의 집합으로 미완성된 설계도라고 생각하면 된다. 이를 완성 시키는 것이 인터페이스의 구현(implements)이다.

  • 개발 코드가 인터페이스 메서드를 호출하면 인터페이스는 구현객체의 메서드를 호출한다.
  • 인터페이스를 구현하기 위해서는 정의된 모든 추상메서드를 구현해야 한다.
  • 일부 추상 메서드만 구현할 경우 해당 클래스는 abstract class가 된다.

인터페이스의 모든 메서드는 public접근제어자를 가진다. 구현 클래스에서 오버라이딩할 때 오버라이딩 메서드의 접근제어자 조상보다 좁아서는 안된다.
구현 클래스가 작성되면 new 연산자로 객체를 생성할 수 있다. 인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야한다.
다형성은 조상타입의 참조변수가 자손타입의 인스턴스를 가리키는 형태를 말한다.

// 부모타입(Tv)로 자손타입(SmartTv) 인스턴스를 가리킨다.
Tv t = new SmartTv();
interface Fightable {
    void move(int x, int y);
    void attack(Fightable f);
}

class Fighter implements Fightable {

    public void move(int x, int y){}
    public void attack(Fightable f){}

    // 자손 클래스에서 추가된 메서드
    public void skill(){}
}
  1. 인터페이스 타입으로 자손 타입의 인스턴스를 가리킬 수 있다.

    • 부모 타입 (Fightable)의 참조변수를 사용하면 자손 타입 (Fighter)에 얼마나 많은 멤버가 있든 부모의 멤버만 사용이 가능하다.
    • 인터페이스 변수는 참조타입이므로, 구현 객체가 대입될 경우 구현 객체의 주소를 저장한다.
Fightable f = new Fighter();

// 자손 클래스에만 존재하는 메서드를 호출할 시 컴파일 에러가 난다.
f.skill()  // 컴파일 에러
  1. 인터페이스를 메서드 반환타입으로 지정할 수 있다.

    • 메서드의 반환타입이 인터페이스라면 해당 인터페이스를 구현한 클래스 타입의 인스턴스를 반환한다.
    • 코드의 유연성을 높이기 위해서 사용한다.
class Fighter implements Fightable {
    @Override
    public void attack(Fightable f) {}
}

class Exmaple {
    public Fightable test() {
        Fighter f = new Fighter(); // 인터페이스 구현 클래스의 인스턴스
        return f;        // return (fightable) f; 자동으로 부모로 형변환된다. (업캐스팅)
    }
}

class Sample {
    void Test() {
        Example exmaple = new Example();
        Fightable f = b.test(); 
    }
}

익명 구현 객체

자바에서는 소스파일을 만들지 않고도 인터페이스의 구현 객체를 만들 수 있는 방법을 제공한다.
아래는 익명 구현 객체를 생성하여 인터페이스 변수에 대입하는 코드로 하나의 실행문이므로 끝에는 ;을 반드시 붙여야 한다.

인터페이스 변수 = new 인터페이스() {
    // 인터페이스에 선언된 추상 메서드의 실체 메서드 선언, 작성 안하면 컴파일 에러가 난다.
    // 필드와 메서드를 선언할 수 있지만 익명 객체 안에서만 사용이 가능하고 인터페이스 변수로 접근할 수 없다
};
  • new연산자 뒤에는 클래스 이름이 와야하지만 클래스 이름이 없는것이 특징이다.
  • {}에는 인터페이스에 선언된 모든 추상 메서드들을 구현해야한다. 그렇지 않으면 컴파일 에러가 난다.
  • 필드와 메서드를 선언할 수는 있지만 익명 객체 안에서만 사용이 가능하다. 접근도 불가능하다.

    모든 객체는 클래스로부터 생성되는데, 익명 구현 객체도 예외는 아니다.
    자바 컴파일러에 의해 자동으로 클래스파일이 만들어진다. 클래스 파일에 $1, $2 같은 문자가 추가된다.

다중 인터페이스 구현 클래스

객체는 다수의 인터페이스 타입으로 사용할 수 있다. 다중 인터페이스 구현 시, 구현 클래스는 모든 인터페이스의 추상 메서드에 대해 실체 메서드를 작성해야 한다.

인터페이스 상속

인터페이스도 다른 인터페이스를 상속받을 수 있다.
extends키워드를 사용하여 상속받을 수 있다. 클래스와 다르게 다중 상속이 가능하다.

public interface SubInterface extends SuperInterface1, SuperInterface2 {...}

인터페이스 상속 시 주의할 점
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메서드 뿐만 아니라 상위 인터페이스의 모든 추상 메서드들을 구현해야한다. 모든 추상 메서드들을 구현해야 구현 클래스로부터 객체를 생성하고 나서 하위 및 상위 인터페이스 타입으로 형변환이 가능하다.

// ClassImpl : 구현 클래스
SubInterface a = new ClassImpl();
SuperInterface1 b = new ClassImpl();
SuperInterface2 c = new ClassImpl();

하위 인터페이스로 형변환이 되면 상, 하위 인터페이스에 선언된 모든 메서드들을 사용할 수 있다. 상위 인터페이스로 형변환 시 하위 인터페이스의 메서드는 사용이 불가능 하다.

인터페이스의 역할

  1. 두 대상(객체)의 중간 역할
    • 예시) 사용자 - GUI - 컴퓨터
      • 컴퓨터를 직접 다루기 어렵기 때문에 그래픽 화면을 통해 좀더 쉽게 다룰 수 있게 된다. 중간역할을 한다.
    • 예시) 운전자 - 프레임 - 자동차
      • 디젤 및 전기차등 자동차 내부적으로는 바뀌어도 자동차의 프레임, 동작 방법이 바뀌지 않으면 운전자는 영향을 받지 않는다.
  2. 선언(설계)와 구현의 분리
    • 껍데기와 알맹이가 같이 붙어있던 형태를 인터페이스를 사용하여 인터페이스 (껍데기), 클래스 (알맹이)로 분리
    • 분리하지 않으면 유연하지 않고 변경에 있어 불리하지만 분리가 되어있으면 알맹이 (클래스)를 다른것으로 바꾸기가 쉽다. 즉 코드가 유연해진다.

선언과 구현을 동시에

class Example {
    public void test() {
        System.out.println("Example의 test()");
    }
}

선언과 구현을 분리

// 선언(설계)
interface Sample {
    public void test();
}

// 구현
class Example implements Sample {
    public void test() {
        System.out.println("Example의 test()");
    }
}

강한 결합과 느슨한 결합

강한결합과 느슨한결합
출처 : https://ahnyezi.github.io/java/javastudy-8-interface/

왼쪽에 강한결합을 살펴보면 A는 B에 의존하고 있는 상황이다. 이때 A가 C를 사용하게 하려면 B에 의존하고 있던 코드를 C에 의존하게끔 변경해야 한다.
반면 느슨한 결합은 A는 인터페이스 I를 의존하고 있고 I 인터페이스를 구현한 B를 사용하고 있다. 이때 A가 C를 사용하게 하려면 A는 I에 의존하고 있기 때문에 I 인터페이스를 구현한 C를 사용한다면 코드를 변경하지 않아도 A는 C를 사용할 수 있다.

  • 강한 결합 : 빠르지만 변경에 불리하다.
  • 느슨한 결합 : 느리지만 유연하고 변경에 유리하다.

강한 결합 (A -> B)

class A {
    public void methodA(B b){
        b.methodB();
    }
}

class B {
    public void methodB() {
        System.out,println("methodB()");
    }
}

느슨한 결합 (A -> I -> B)

class A {
    // I인터페이스를 사용하여 A클래스는 B클래스와 관계가 없다.
    public void methodA(I i) {
        i.methodB();
    }
}

interface I {
    public abstract void methodB();
}

class B implements I {
    public void methodB() {
        System.out.println("methodB()");
    }
}

class C implements I {
    public void methodB() {
        System.out.println("methodB() in C");
    }
}
  1. 개발시간 단축
    강한결합 형태는 A가 B를 직접 의존하고 있기 때문에, B가 완성된 후에 A를 개발할 수 있다. 하지만 느슨한 결합형태는 B가 완성되지 않아도 인터페이스 I를 이용해서 A를 개발할 수 있다.
    • 인스턴스 변수는 메서드를 이용해서 접근한다. (캡슐화된 형태에서 getter, setter를 이용해 데이터 전달)
  2. 표준화
    표준화가 가능하다. 대표적으로 JDBC(Java Database Connectivity) API (Application Programming Interface)가 있다.
  3. 관계 없는 클래스들 관계생성
    여러가지 클래스에 공통적인 메서드를 추가하고싶을 경우 빈 인터페이스를 작성하여 여러가지 클래스에서 해당 인터페이스를 implements하게 한다. 인터페이스를 구현한 클래스들에게 공통점이 생긴다.

인터페이스의 default 메서드 - 자바 8

자바 8 이전에 인터페이스가 가질 수 있는 메서드는 추상 메서드뿐이었다. 해당 인터페이스를 구현한 클래스에서 메서드의 구현부를 완성해야 메서드를 사용할 수 있었다.
문제점은 구현 클래스들이 메서드의 구현부를 작성할 때, 그 내용이 일치하더라도 클래스 마다 새로 구현부를 적어줘야 한다는 것이다. 이 문제를 해결하기 위해 default 메서드를 도입하게 되었다. default 메서드는 인터페이스 내부에 존재할 수 있는 구현 메서드이다. implements한다면 따로 메서드를 구현하지 않아도 사용할 수 있다.

interface TestInter {
    default void test() {
        System.out.println("디폴트 메서드 실행");
    }
}

class TestClass implements TestInter {
    public static void main (String[] args) {
        TestClass testClass = new TestClass();
        testClass.test();  // 디폴트 메서드 실행
    }
}

인터페이스의 static 메서드 - 자바 8

인터페이스의 static 메서드는 인터페이스 내에서 이미 구현부를 구현한 메서드인 점은 default 메서드와 동일하다. 하지만 인터페이스를 구현한 클래스에서 오버라이딩하여 사용할 수가 없다.

interface TestInter {
    static void test() {
        System.out.println("static 메서드 실행");
    }

    void hello(String msg);
}

public class TestClass implements TestInter {
    @Override
    public void hello(String msg) {
        System.out.println("추상 메서드 구현 : " + msg);
    }

    public static void main(String[] args) {
        TestClass testClass = new TestClass();

        TestInter.test();                    // static 메서드 실행
        testClass.hello("구현 메서드 실행")    // 추상 메서드 구현 : 구현 메서드 실행
    }
}

인터페이스의 private 메서드 - 자바 9

자바 9 부터는 인터페이스 내에 private 메서드를 가질 수 있게 되었다.
인터페이스에서 사용 가능한 멤버

  1. 상수
  2. abstract 메서드
  3. default 메서드
  4. static 메서드
  5. private 메서드
  6. private static 메서드

private 메서드는 오직 해당 인터페이스 내에서만 접근이 가능하고, 인터페이스를 상속받은 클래스나 서브 인터페이스에서는 접근할 수 없다.

참고
https://blog.baesangwoo.dev/posts/java-livestudy-8week/
https://github.com/yeGenieee/java-live-study/blob/main/%5B8%5DJava%20Live%20Study.md
https://dev-coco.tistory.com/13
https://ahnyezi.github.io/java/javastudy-8-interface/

반응형

'Programming > JAVA' 카테고리의 다른 글

멀티 쓰레드 프로그래밍이란?  (0) 2021.03.01
예외란?  (0) 2021.02.13
패키지란?  (0) 2021.01.21
상속이란?  (0) 2021.01.07
클래스란?  (0) 2021.01.04