인터페이스 정의하는 방법
interface 키워드 사용하여 정의한다.
자바 8 이전에는 추상 메소드와 상수만을 정의할 수 있다. 자바 8 이후에 default 메소드 기능이 추가되어 인터페이스 내부에서 구현체를 정의할 수 있게 되었다.
public interface TestInterface {
public static final int variable = 1; // public static final 키워드 생략 가능
public abstract void abstractMethod(); // public abstract 키워드 생략 가능
default void defaultMethod() { // default 키워드 사용
System.out.println("default method");
}
}
인터페이스 구현하는 방법
implements 키워드 사용하여 인터페이스를 구현한다.
@Override 를 따로 적어주지 않아도 동작은 하지만, 명시적으로 적어주는 것이 좋은 습관이다.
public class TestInterfaceImpl implements TestInterface {
@Override
public void abstractMethod() {
System.out.println("abstract method");
}
public void method() {
System.out.println("method");
}
public void callDefaultMethod() {
TestInterface.super.defaultMethod(); // 이런 식으로 인스턴스화 없이 디폴트 메소드를 호출할 수 있다.
}
}
그리고, 자바에서는 다중 상속은 되지 않는다. 하지만 인터페이스는 다음과 같이 여러 개를 구현할 수 있다.
public interface InterfaceA {
void printA();
}
public interface InterfaceB {
void printB();
}
public class InterfaceImpl implements InterfaceB, InterfaceA {
public static void main(String args[]) {
InterfaceA implA = new InterfaceImpl();
implA.printA();
InterfaceB implB = new InterfaceImpl();
implB.printB();
}
@Override
public void printA() {
System.out.println("a");
}
@Override
public void printB() {
System.out.println("b");
}
}
인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
변수를 인터페이스 타입으로 선언하고 구현한 인스턴스를 만들어 넣어주어 사용할 수 있다.
대신, 인터페이스에 선언된 메소드 및 변수만을 사용할 수 있다.
public static void main(String args[]) {
TestInterface test = new TestInterfaceImpl();
test.abstractMethod();
test.method(); // compile error: Cannot resolve method 'method' in 'TestInterface'
TestInterfaceImpl testImpl = new TestInterfaceImpl();
testImpl.method(); // 이렇게 해야 method 함수를 호출할 수 있다.
}
인터페이스 레퍼런스로 구현체를 사용할 때에 위의 예제에서 testInterfaceImpl 클래스에 선언된 callDefaultMethod 를 호출할 수 없다.
사용하려면 구현체 타입을 통해 구현체를 사용해야 한다.
인터페이스 상속
한 인터페이스를 다른 인터페이스가 상속 받을 수 있다.
인터페이스의 기본 메소드, 자바8
자바8 이전에는 인터페이스에는 추상 메소드만 선언할 수 있었다. 구현체가 있는 메소드를 정의하지 못 했다.
자바8부터는 구현체가 있는 메소드도 선언할 수 있다.
public interface InterfaceA {
default void printDefault() {
System.out.println("a default");
}
}
그렇다면, 언제 default 메소드를 사용하면 좋을까?
내 생각으로는 구현체 모두가 사용하는 간단한 구현체 메소드가 필요한 경우 유용할 것 같다. 이미 인터페이스를 여러 클래스가 구현하는 구조로 되어 있는 경우, 인터페이스에 메소드를 한 개 추가 해야 할 경우, 구현하기 위해 추가로 클래스를 만들고, 그 클래스를 상속받도록 수정해야 한다.
이러한 작업이 생략될 수 있을 듯 하다. 그냥 인터페이스에 디폴트 메소드를 구현하고, 구현체 클래스들에서 사용하면 되기 때문이다.
또한, 선택적으로 메소드를 구현하게 해준다. default 메소드로 인터페이스에서 구현해놓으면 (빈 구현체로) 구현체는 모든 메소드를 구현하지 않아도 되는 편의성이 제공된다.
예전에는 중간에 추상클래스를 선언 및 메소드를 구현하는 단계가 필요했었는 데, 생략 가능하게 되었다.
자바에서는 인터페이스 다중 구현이 가능하다.
그렇다면, 두 가지 인터페이스를 다중 구현을 했을 때에 두 인터페이스가 메소드 시그니처가 같은 디폴트 메소드를 가지고 있다면 어떻게 될까?
public interface InterfaceB {
default void printDefault() {
System.out.println("b default");
}
}
구현체에서 interfaceA 와 interfaceB 를 다중구현하려고 할 때, 컴파일 에러가 발생한다.
me.screw.javademostudy.javainterface.InterfaceImpl inherits unrelated defaults for printDefault() from types me.screw.javademostudy.javainterface.InterfaceB and me.screw.javademostudy.javainterface.InterfaceA
컴파일러 입장에서는 어떤 메소드를 선택해야할 지 애매하다고 보는 것 같다.
그래서 구현체에서는 printDefault 메소드에 대해 재정의해줘야 한다.
public class InterfaceImpl implements InterfaceB, InterfaceA {
@Override
public void printDefault() {
// InterfaceB.super.printDefault(); // choose interfaceA's
// InterfaceB.super.printDefault(); // choose interfaceB's
System.out.println("override impl");
}
}
default 메소드를 유용하게 사용하려면 이름을 지을 때 많은 고민이 필요해보인다.
왜냐하면, default 메소드의 의미가 많이 퇴색된다고 생각하기 때문이다. default 메소드를 사용하는 이유를 생각해보면 클래스에 수정없이 기능을 확장하고 싶은 건데, 구현 클래스에서 디폴트 메소드 재정의가 필요해지는 상황이 생긴다.
인터페이스의 static 메소드, 자바 8
static 메소드도 default 메소드와 마찬가지로 인터페이스에서 메소드 구현을 할 수 있다.
대신에 객체 자체에 종속되는 개념이 아니다. 그래서 인터페이스를 구현하는 클래스에서 재정의를 할 수 없을 뿐더러, 인터페이스를 구현한 구현체에서 호출할 수 없다.
인터페이스 자체에서는 호출할 수 있다.
public interface InterfaceA {
void printA();
default void printDefault() {
System.out.println("a default");
}
static void printStatic() {
System.out.println("a static");
}
}
public class InterfaceImpl implements InterfaceA {
public static void main(String args[]) {
InterfaceA implA = new InterfaceImpl();
implA.printA();
implA.printDefault();
implA.printStatic(); // compile error
InterfaceA.printStatic(); // use instead
}
@Override
public void printA() {
System.out.println("a");
}
@Override
public void printDefault() {
System.out.println("impl default");
}
@Override // override error
public void printStatic() {
System.out.println("impl static");
}
}
상위 인터페이스의 default 메소드를 하위 인터페이스에서 static 으로 구현할 수 없다.
인터페이스의 private 메소드, 자바 9
일반적인 클래스에서처럼 동작한다. 인터페이스에서는 메소드를 구현할 수 있는 곳에서 사용가능하다.
예를 들면, default 메소드나 static 메소드에서 사용 가능하다. 대신 static 메소드에서 사용하려면 private 메소드도 static 이어야 한다.
public interface InterfaceA {
void printA();
default void printDefault() {
System.out.println("a default");
printPrivate();
}
private void printPrivate() {
System.out.println("a private");
}
static void printStatic() {
System.out.println("a static");
printStaticPrivate();
}
private static void printStaticPrivate() {
System.out.println("a private static");
}
}
private 메소드 주요 이점은 인터페이스의 encapsulation 이다. 인터페이스에서 메소드를 구현할 수 있는 기능이 추가됨에 따라 자연스레 추가된 기능이 아닐까 생각해본다. 또 다른 이점은 재사용성에도 있다.