인터페이스는 객체와 외부의 상호 작용을 추상화한 것이라 보면 된다. 즉, 인터페이스는 기본적으로 상수와 추상 메서드의 집합이며 구현 클래스는 동작을 구현하는 것으로 상호 작용을 정의한다.
interface TV {
public void powerOn();
public void powerOff();
}
이때 각 메서드는 추상 메서드이지만 인터페이스 안에 있어서 public만 명시해줘도 public abstract final으로 간주된다.
특정 인터페이스를 구현하기 위해서는 동작을 정의할 클래스가 있어야한다. 그리고 implements 라는 키워드를 통해 구현할 인터페이스를 지정하면 된다.
public class RemoteController implements TV {
@Override
public void powerOn(){
System.out.println("Power ON");
}
@Override
public void powerOff(){
System.out.println("Power OFF");
}
public void click(){
System.out.println("Button Clicked");
}
}
위의 예시처럼 인터페이스를 구현하기 위해서는 인터페이스가 가지고 있는 추상 메서드들을 모두 오버라이딩해서 구현해야 한다.
익명 구현 객체는 재사용이 필요없어 파일단위의 관리 없이 1회성으로 인터페이스를 구현하는 객체를 의미한다. 즉, 별도의 소스 파일 없이도 구현 객체를 만들 수 있는 방법이다. 기본 사용법은 아래와 같다
인터페이스 변수 = new 인터페이스(){
// 추상 메서드들 모두 구현
};
주의할 점은 객체를 만드는 것이기 때문에 마지막에 세미콜론(;)을 해주어야 한다. 주로, Java 8의 람다식이나 UI 프로그래밍에서 이벤트 처리용으로 많이 쓰인다.
인터페이스가 객체와 외부의 상호 작용을 추상화 한것이기에 어떠한 구현 클래스가 와도 외부에서 동작을 실행시킬 수 있다는 뜻이고 인터페이스 레퍼런스(참조 변수)는 다양한 자신을 구현한 어떠한 클래스라도 참조 할 수 있다. 예제로는 흔히 쓰이는 컬렉션 프레임워크의 List를 예시로 들었다.
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
public class Main{
public static void main(String args[]){
// list as an ArrayList
List<Integer> list = new ArrayList<Integer>();
// list as a LinkedList
list = new LinkedList<Integer>();
}
}
위처럼 List 인터페이스를 구현하는 ArrayList와 LinkedList 타입 객체는 List 타입으로 업캐스팅 되어 List 레퍼런스가 가리킬 수 있다. 다만, 인터페이스를 구현한 클래스를 레퍼런스로 쓰지 않고 인터페이스를 레퍼런스로 쓸 때 주의할 점은, 구현 클래스가 가지고 있는 고유한 메서드는 인터페이스 레퍼런스에서 사용할 수 없다는 점이다.
우선 자바에서 흔히 말하는 상속이란, 클래스 간의 상속에 관한 얘기이다. 클래스 간 상속은 기본적으로 다단계는 가능하지만 다중 상속은 안된다. 하지만, 인터페이스는 인터페이스 사이의 다중 상속을 지원한다.
하지만, 모든 경우에서 다중 상속이 가능한 것은 아니다. 메서드 시그니처가 같은데 리턴 타입이 다르면 컴파일 에러가 난다.
그래도 왜 클래스에서는 다중 상속을 지원하지 않는데 인터페이스에서는 다중 상속을 지원할까
다중 상속을 하게 되면 하나의 클래스가 여러 상위 클래스를 상속 받는 과정에서 위와 같은 관계가 설정될 수 있다. 이때 만약 조상 클래스에서 정의한 Func() 라는 메서드가 있고 각 부모 클래스가 필요에 의해 따로 재정의하였다고 하자. 그러면 자식 클래스는 어떤 Func()의 동작을 수행해야하는지 알 수 없기에 충돌이 발생한다.
하지만, 인터페이스는 추상 메서드들의 집합이다. 즉, 다중 상속의 조건에 위배되지 않는 경우 실체가 없는 메서드들이기 때문에 충돌이 발생하지 않는다.
자바 8 에서 추가된 Default Method는 기존의 추상 메서드 집합이라는 개념과는 위배된다. 아래의 예시를 보자.
interface TV {
public void powerOn();
public void powerOff();
default void soundUp(){
System.out.println("Sound Up");
}
}
주목할 점은 TV 인터페이스에서 default method인 soundUp은 구현부가 존재한다. 그럼 왜 이러한 기능을 자바 8에서 추가한지를 알아보자.
인터페이스에 구현부가 존재하는 메서드가 있다는 것은 이 인터페이스를 구현할 클래스에서는 따로 구현할 필요없이 바로 쓸 수 있다. 그럼 애초에 추상 클래스로 만들면 되지 않았나라고 생각할 수 도 있다. 하지만 이 기본 메서드를 사용하면 좋은 시기는 인터페이스를 처음 정의할 때가 아니다.
이미 구현이 되어진 인터페이스에 구현 클래스들에 새로운 기능을 추가해야 한다고 생각해보자. 기존 방식이라면 메서드를 선언하고 각 구현 클래스에서 일일이 동작을 구현해야 했다. 하지만 기본 메서드를 사용하면 기존 인터페이스를 수정하는 것만으로 모든 구현 클래스에서 바로 이 기능을 사용할 수 있다. 그리고 필요에 의해 구현 클래스에서 재정의도 가능하다.
이것의 장점은 개발 생산성 이외에도 시스템 안정성에서도 이점이 있다. 기본 메서드는 이 인터페이스를 구현하는 모든 클래스에 동일하게 적용된다. 즉, 구현 클래스들은 수정된 새로운 인터페이스와의 충돌을 걱정할 필요가 없게 된다.
하지만, default method를 쓸 때 주의할 점이 있다. 바로 인터페이스 다중 상속에서의 다이아몬드 문제이다.
static 리턴타입 메소드명 (매개변수) {
// 기능
}
Static Method는 default method와 같이 구현에 구애받지 않고 정의할 수 있지만 외부에서 재정의가 불가능한 메서드이다. 그리고 인터페이스의 인스턴스(구현 객체)가 없이도 메서드를 호출할 수 있는 장점이 있다.
interface Calculator {
static int sum(int a, int b) {
return a+b;
}
}
public class Main{
public static void main(String args[]){
int result = Calculator.sum(1,2);
}
}
주의할 점은 static method를 다른 메서드에서 호출할 때는 같은 static만 가능하다.
자바 9 에서는 private method 와 private static method가 추가되었다. 지금까지의 메서드들은 기본적으로 외부에서 접근이 허용되는 메서드들이었다. 즉, 인터페이스에 등록되는 메서드가 외부에 알려지지 않는 방법(캡슐화)이 없었다. 그래서 private 접근 제어자가 추가 되었고 이를 활용해 오직 인터페이스 내부에서 자체적으로 동작을 수행할 수 있게 되었다.
interface Calculator {
public void powerOn();
default int result(a,b){
return add(a,b);
}
private int add(int a, int b){
return a + b;
}
}
보면 알 수 있듯이 private method 또한 구현부를 가지고 있다. 그러나 이전 같으면 두개의 default 메서드를 활용하는 것이었겠지만 private 메서드가 추가되면서 외부에서는 add()의 존재를 알 수 없지만 내부에서는 add의 기능을 사용한다.