카테고리 없음

[Java] 인터페이스

운체 2020. 3. 14. 14:57

인터페이스

추상 클래스보다 추상화 정도가 높은 일종의 추상 클래스라고 생각하면 된다. 또한 다중 상속을 가능하게 해 준다! 아무래도 자바에서 인터페이스가 중요한 부분을 차지하다보니 이번 정리는 꽤 길어질 것 같다.

추상 클래스와의 차이점은 다음과 같다.

  • 일반 메소드나 변수를 가질 수 없다. => 추상화 정도가 더 높다고 볼 수 있는 점이다.
  • 추상 메소드와 상수만을 허용한다.
    • public static final
    • public abstract method();
    • 제어자의 생략이 가능하며 컴파일러가 자동으로 추가해준다.
interface 인터페이스이름 {
  public static final type = value;
  public abstract methodname(parameter);
}

인터페이스의 구현

인터페이스도 추상 클래스처럼 인스턴스를 생성할 수 없으며 상속을 통해 메소드를 완성해야 한다. 상속 시 extends를 사용하지 않고 implements를 사용한다.

class Child implements Parent {
	...
}
  • 인터페이스에서 일부의 메소드만 구현한다면, abstract를 붙여서 추상 클래스로 선언해주어야 한다.

인터페이스를 통한 다중 상속

인터페이스는 static 상수만 정의가 가능하므로 조상 클래스와 충돌하는 경우는 거의 없으며 충돌한다고 하더라도 클래스 이름으로 구분이 가능하다. 메소드의 선언부가 일치하는 경우에는 선언부만 존재하므로 조상클래스의 메소드를 상속받으면 된다.

만약 다중 상속을 받으면서 충돌이 발생한 상황이라면, 두 조상클래스 중에서 비중이 높은 쪽을 선택하고 다른 쪽은 클래스 내부에 멤버로 포함시키거나 필요한 부분을 뽑아서 인터페이스로 만든 다음 구현하도록 한다.

인터페이스를 이용한 다형성

인터페이스 또한 이를 구현한 클래스의 조상이 되므로, 해당 인터페이스 타입의 참조 변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있다. Fightable 인터페이스를 Fighter클래스가 상속받아 구현했다고 가정하자.

Fightable f = new Fighter(); // 조상 클래스 (인터페이스) 타입의 참조변수에 자손 클래스를 담았다.

이뿐만 아니라 메소드의 매개변수로도 들어갈 수 있다.

void attack(Fightable f) { // Fightable 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야한다.
	...
}

아래와 같이 리턴 타입으로도 사용이 가능하다!

Fightable method() {
  Fighter f = new Fighter();
  return f
}

리턴 타입이 인스턴스라는 것은 메소드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

인터페이스의 장점

그렇다면 인터페이스의 장점은 무엇이고 왜 자주 사용하게 되는 것일까? 그 이유는 다음과 같다.

  1. 개발 시간의 단축
    • 인터페이스가 완성이 되면, 프로그램의 작성이 가능하다. 메서드를 호출하는 쪽에서는 선언 부만 알면 되기 때문이다. 클래스가 완성될 때까지 기다릴 필요 없이 동시 개발이 가능해지는 것이다.
  2. 표준화 가능
    • 인터페이스는 기본적인 틀을 제공하는 것이며, 이를 통해 여러 사람이 개발에 참여해도 일관적이고 정형화된 프로그램 개발이 가능해진다.
  3. 관계없는 클래스에게 관계를 맺을 수 있음
    • 상속관계나 같은 조상 클래스가 없는 관련 없는 클래스에 하나의 인터페이스를 공통적으로 구현함으로써 관계를 맺어줄 수 있다.
  4. 독립적인 프로그래밍이 가능
    • 클래스의 선언과 구현을 분리할 수 있다. 클래스 간의 관계를 인터페이스를 통해 간접적으로 만들어 독립적으로 프로그래밍이 가능해지는 것이다.
    • 데이터 베이스와 관련된 인터페이스를 정의하고 이를 이용하여 프로그램을 작성하면 추후 데이터 베이스가 변경되더라도 프로그램을 변경하지 않아도 된다.

인터페이스의 이해

  • 클래스를 제공하는 쪽(Provider)과 사용하는 쪽(User)이 있어야 한다.
  • 메소드를 사용하는 쪽은 메소드의 선언 부만 알면 된다.
class A { // User. B의 메소드를 사용하는 쪽이다.
  public void method(B b){
    b.methodB();
  }
 }
 // A가 매개변수로 B 인스턴스를 받으므로 직접적인 관계에 있다고 볼 수 있다.
 class B {
  public void methodB(){ // Provider. A에게 메소드를 제공하는 쪽이다.
    System.out.println("methodB()");
  }
}

이제 인터페이스를 사용하여 간접적으로 바꾸어보자.

Interface I {
  public abstract void methodB();
}
class A { 
  public void method(I i){ // 매개변수가 I가 들어간 것을 통해 다형성을 살펴볼수 있는 부분이다.
    i.methodB();
  }
 }
 class B implements I {
  @Override
  public void methodB(){
    System.out.println("methodB()");
  }
}

A와 B가 직접적으로 연결되었던 관계에서 인터페이스를 거치는 A-I-B의 간접적인 관계가 완성되었다. 인터페이스 덕분에 A는 사실상 B 클래스의 이름을 몰라도 메소드 사용이 가능해지게 되었다!

 

디폴트 메소드

인터페이스에 새로운 메소드를 추가하게 되면 추가된 메소드를 구현하면 된다고 생각할 수 있지만.. 현실은 그렇게 호락호락하지 않다. 이 인터페이스를 구현한 모든 클래스에 추가된 메소드를 구현해야 하기 때문이다. 간단한 실습에서는 큰 문제가 되지 않지만, 대규모 프로젝트나 코드라면 이를 하나하나 추가하기란 고문이 따로 없을 것이다.

아무리 인터페이스를 훌륭하게 설계해도 시간이 지나면 한 번쯤은 변경할 일이 생기기 때문에 디폴트 메소드라는 것이 등장했다! 디폴트 메소드는 추상 메소드의 기본적인 구현을 가능하게 해 준다.

  • 지금까지 추상 메소드는 구현부가 존재하면 에러가 발생했다.

디폴트 메소드를 사용하면 인터페이스를 구현한 클래스를 변경하지 않아도 된다. 사용법은 앞에 default 키워드를 붙여주면 된다.

interface MyInterface {
  void method();
  default void newmethod(){...};
}

메소드의 충돌

새로 추가한 디폴트 메소드가 다른 인터페이스에 존재하거나, 조상 클래스의 메소드와 충돌한다면 어떻게 될까? 메소드 이름의 충돌은 충분히 가능성 있는 일이다.

  1. 인터페이스 간의 충돌 ⇒ 인터페이스를 구현한 클래스에서 오버라이딩을 하자.
  2. 조상 클래스와의 충돌 ⇒ 조상 클래스의 메소드가 상속되고 디폴트 메소드는 무시된다.

아직 자바에 대해 익숙하진 않지만 공부를 할수록 정말 객체지향을 위한 언어구나 싶은 느낌이 든다. 더 열심히 공부해야겠다.