자바의 인터페이스에 대해 학습하세요.
인터페이스의 내용에 대해 정리하기 전에 인터페이스(Interface)가 무엇인지 부터 짚고 넘어가보자. 정확히 정의하기는 어렵지만, 인터페이스는 기본적으로 추상 메소드들을 모아놓은 것이라고 볼 수 있다.
인터페이스를 사용하므로써 개발자들은 구현체들의 공통 부분을 추상화 할 수 있고, 일종의 코드 규약을 강제할 수 있다.
인터페이스의 선언은 클래스와 비슷하다.
public interface Animal {
boolean canMove = true; // 상수
public String cry(); // 메소드
public String getFeed(); // 메소드
}
와 같이 선언한다. 그런데 보다싶이 인터페이스 안에는 메소드의 선언만 있고 구현이 없다. 앞서 말했듯이 인터페이스는 추상 메소드의 모음이기 때문에, cry 메소드는 컴파일 과정에서 abstract 키워드가 붙게 되며, 메소드를 선언만 하고 실제 구현은 다음과 같이 인터페이스를 구현하는 클래스에서 메소드의 내용을 구현하게 된다.
public class Lion implements Animal {
@Override
public String cry() {
return "어흥";
}
@Override
public String getFeed() {
return "고기";
}
}
인터페이스를 구현하는 클래스는 implements 키워드를 사용하며, 이 클래스에는 반드시 구현하는 인터페이스의 모든 메소드를 오버라이딩 해야 한다.
또한 인터페이스 내부의 상수에 대해 살펴보면 기본적으로 인터페이스 안에 선언하는 상수에는 컴파일 과정에서 기본적으로 public static final이 붙는다. 즉, 상속하더라도 이 상수는 수정할 수 없다.
인터페이스의 구현은 상속과 비슷하지만 결정적인 차이가 있는데, 다중 상속이 금지되어 있는 클래스끼리의 상속과 다르게, 한 클래스가 여러개의 인터페이스를 구현할 수 있다는 것이다. Animal 인터페이스 외에 Life 인터페이스가 있다고 하자.
public interface Life {
public void breathe();
}
이 때 Lion 클래스는 Animal과 Life를 모두 구현한다고 하면,
public class Lion implements Animal, Life {
@Override
public String cry() {
return "어흥";
}
@Override
public String getFeed() {
return "고기";
}
@Override
public void breathe() {
System.out.println("호흡");
}
}
와 같이 두 인터페이스의 메소드들을 오버라이딩 해서 사용할 수 있다. 즉, 인터페이스를 이용해 자바에서 허용되지 않은 다중 상속을 구현할 수 있다. (엄밀히 말하면 상속이라고 할 수는 없지만 상속과 같은 효과를 낼 수 있다.)
Animal 인터페이스를 구현한 Lion 클래스와 Chicken 클래스의 인스턴스를 만든다고 하자.
Lion lion = new Lion();
Chicken chicken = new Chikcen();
일반적으로는 이와 같이 각각의 클래스를 레퍼런스로 하여 인스턴스를 만들 수 있다. 그런데, 인터페이스를 이어받아 구현한 클래스들의 인스턴스들은 레퍼런스 타입으로 인터페이스를 지정할 수 있다.
Animal lion = new Lion();
Animal chicken = new Chicken();
이렇게 Animal 인터페이스를 타입으로 하여 인스턴스를 생성하면, 매개변수에 Animal 인터페이스가 들어가는 메소드에 lion과 chicken 모두를 넘겨줄 수 있다.
그런데 구현체 Lion 클래스 안에 Animal 인터페이스에 선언되지 않은 다른 메소드가 존재한다고 해보자.
public class Lion implements Animal {
...
public void character() {
System.out.println("갈기가 있다");
}
}
character() 메소드는 오버라이딩 한 메소드가 아니어서 Animal 인터페이스에는 없다. 이 때, 인스턴스 lion은 Lion 클래스를 이용해 생성했지만, Animal 타입이기 때문에 lion에서 이 메소드를 호출하려고 하면 오류가 난다. 이 때 타입 캐스팅을 해주면 정상적으로 사용할 수 있다.
Animal lion = new Lion();
((Lion)lion).character(); // "갈기가 있다" 출력
인터페이스끼리도 상속이 가능하다. 이전에 만들었던 Animal 인터페이스를 상속받아 인터페이스를 더 확장하는 Bird 인터페이스를 만들어보자.
public interface Bird extends Animal {
public void fly();
}
이렇게 클래스의 상속처럼 extends 키워드를 이용해서 인터페이스 끼리도 상속을 할 수 있다. 또한 인터페이스 상속의 특징으로, 인터페이스 끼리는 다중상속이 가능하다.
위에서 인터페이스를 구현한 클래스는 인터페이스를 레퍼런스 타입으로 쓸 수 있다고 했는데, 인터페이스끼리 상속 관계가 있는 경우 부모 인터페이스를 타입으로 쓸 수 있다. 예를들어, Bird 인터페이스를 구현한 Eagle 이라는 클래스는 Bird 타입으로도 쓸 수 있고, Animal 타입으로도 쓸 수 있다.
인터페이스는 추상 메소드의 모음이라고 설명했고, 그에 따라 기본적으로 인터페이스 안의 메소드들은 컴파일 시 abstract 메소드가 된다고 설명했다. 그런데 자바 8버전 부터는 default 메소드가 추가되었다.
기본 메소드는 인터페이스의 하위 호환성을 위해 추가되었으며, 추상 메소드와 달리 일반적인 메소드처럼 {} 안에 구현부가 존재해야 한다. default 메소드로 구현된 메소드는 이미 구현되어 있기 때문에 인터페이스를 구현하는 클래스에서 새로 구현하지 않아도 오류가 나지 않는다. 그러나 만약 클래스에서 default 메소드를 수정해야 한다면 메소드 오버라이딩을 통해 메소드의 구현 내용을 수정할 수 있다.
만약 여러 인터페이스를 구현해서 같은 default 메소드가 여러개 있다면, 오버라이딩해서 default 메소드를 재정의해서 사용해야 한다.
인터페이스 구현과 클래스 상속을 통해 상위 클래스의 메소드와 인터페이스의 default 메소드가 겹쳐서 충돌할 경우, 인터페이스의 메소드는 무시되고 상속 클래스의 메소드를 사용하게 된다.
자바 8에서는 인터페이스에 default 메소드와 함께 static 메소드 또한 추가되었다. static이기 때문에 인스턴스 생성 없이 사용이 가능하며, 상속은 불가능하다. 또한 기본적으로 접근 제어자는 public으로 설정되어 있다.
public interface Animal {
static String getDefinition() {
return "움직이는 생물";
}
...
}
// 메소드 사용
System.out.println(Animal.getDefinition()); // "움직이는 생물" 출력
자바 9에서는 인터페이스에 private 메소드가 추가되었다. 사실 기존의 인터페이스라면 필요하지 않은 기능이겠지만, 자바 8에서 default 메소드가 구현되었기 때문에 default 메소드의 로직 분리를 위해서 추가되었다고 볼 수 있다. (해당 용도가 아니면 어차피 사용할 수가 없다.)
private 메소드이기 때문에 구현체에서 구현할 수 없고, 자식 인터페이스로 상속도 불가능하다.
참고로, static 메소드도 private이 가능하다고 한다.