Java의 인터페이스
인터페이스는 일종의 추상 클래스이다. 인터페이스도 추상 클래스처럼 추상 메서드를 갖지만 추상화 정도가 보다 높아 추상 클래스와 달리 일반 메서드 또는 멤버 변수를 구성원으로 가질 수 없고 오직 추상 메서드와 상수만을 가질 수 있다. 추상 클래스를 부분적으로만 완성된 ‘미완성 설계도’라고 한다면, 인터페이스는 밑그림만 그려져 있는 ‘기본 설계도’라 할 수 있다. 인터페이스는 키워드로 class
대신 interface
를 사용하며 접근 제어자로 클래스처럼 public
또는 default
만 허용한다.
interface 인터페이스명 {
public static final 타입 상수명 = 값;
public abstract 메서드명(매개변수...);
}
인터페이스에는 다음과 같은 제약사항이 있다.
public static final
이어야 하며 이는 생략 가능.public abstract
이어야 하며 이는 생략 가능(static 메서드와 default 메서드 제외).해당 제약사항은 인터페이스에 정의된 모든 멤버에 예외 없이 적용 되는 사항이기 때문에 제어자를 생략할 수 있는 것이며, 생략된 제어자는 컴파일 시에 컴파일러가 자동적으로 추가해준다.
interface Implementable {
int YEAR = 2022; // public static final int YEAR = 2022;
void helloWorld(); // public abstract void helloWorld();
}
인터페이스는 인터페이스로부터만 상속 받을 수 있으며, 클래스와 달리 다중 상속이 가능하다. 인터페이스는 클래스와 달리 Object 클래스와 같은 부모가 없다.
interface Implementable1 extends Implementable2, Implementable3 { ... }
클래스의 다중 상속을 불허한 이유는 다이아몬드 문제 처럼 부모 멤버의 출처가 모호해지는 것을 막기 위해서라고 했다. 그렇다면 인터페이스의 다중 상속은 어떻게 가능한 것일까?
예를 들어 다중 상속이 가능한 클래스가 있다고 하자. 이 클래스를 인스턴스화하면 해당 인스턴스는 모든 부모 클래스의 멤버를 상속 받을 것이다. 하지만 만약 서로 다른 부모 클래스의 메서드나 생성자에서 같은 멤버 변수를 초기화하는 경우에는 어떻게 해야할까? 어느 것을 우선으로 해야할까? 인터페이스에는 상수를 제외한 필드는 가질 수 없으므로 이런 문제가 발생하지 않는다. 메서드 역시 구현부가 없기 때문에 다중 상속을 해도 문제가 없는 것이다.
인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상 클래스가 상속을 통해 추상 메서드를 완성하는 것처럼, 인터페이스도 자신에게 정의된 추상 메서드의 바디를 작성할 클래스가 있어야 한다. 추상 클래스를 상속 받는 클래스가 extends
를 사용해 확장한다면 인터페이스는 구현한다는 의미로 키워드 implements
를 사용한다.
class 클래스명 implements 인터페이스명 { ... }
인터페이스를 구현하는 클래스는 인터페이스에 정의된 모든 추상 메서드를 구현해야 하며 일부만 하는 경우 abstract
를 붙여 추상 클래스로 선언해야 한다. 또한 클래스 상속과 인터페이스 구현을 동시에 할 수도 있다.
인터페이스는 인스턴스의 참조 변수, 메서드의 매개변수 및 리턴 타입으로 사용될 수 있다. 인스턴스가 인터페이스를 참조하는 경우는 해당 인터페이스를 구현한 클래스의 인스턴스만 가능하다. 메서드의 매개변수 및 리턴 타입으로 지정된 경우도 해당 인터페이스를 구현한 클래스의 인스턴스를 제공해야 한다.
interface Implementable { ... }
class Impl implements Implementable { ... }
// 인스턴스의 참조 변수
Implementable i = (Implementable) new Impl();
또는
Implementable i = new Impl();
// 메서드의 매개변수
void method(Implementable i) { ... }
// 메서드의 반환 타입
Implementable method() {
...
return new Impl();
}
인터페이스의 장점은 다음과 같이 정리해 볼 수 있다.
원래 인터페이스에는 추상 메서드만 선언할 수 있었으나 JDK 8부터 default 메서드와 static 메서드도 추가할 수 있게 되었다. 인터페이스는 표준화된 프로그래밍을 가능하게 만들지만 추가 사항이 생길 경우 고려해야 할 점이 많다. 인터페이스를 구현한 모든 클래스들이 새로 추가된 내용을 구현해야 하기 때문이다. 이처럼 인터페이스에 변경이 생겼을 때 호환성을 제공하기 위해 default 메서드가 등장하게 되었다. default 메서드는 인터페이스에 기본적인 구현을 제공하는 메서드로 해당 인터페이스를 구현한 클래스에 자동으로 추가되므로 클래스를 변경하지 않아도 된다.
default 메서드는 메서드를 선언할 때 키워드 default
를 붙이며 메서드 바디({ }
)를 가져야 한다. 접근 제어자는 public
만 가능하며 생략할 수 있다.
interface Implementable {
void method(); // 추상 메서드
default void defaultMethod();
}
대신, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있다. 이런 경우 다음과 같은 규칙을 따른다.