클래스를 작성하는 것과 같다. 그러나 class 키워드 대신 interface 키워드를 사용한다. 접근제어자로 public이나 default를 사용할 수 있다
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메소드이름(매개변수목록);
}
일반적인 클래스와는 다르게 인터페이스에는 몇 가지 제약사항이 있다.
public static final이어야 하며, 생략할 수 있다.public abstract이어야 하며, 생략할 수 있다.static 메소드와 디폴트 메소드는 예외( JDK 1.8부터 )인터페이스에 정의된 모든 멤버에 예외없이 적용되는 사항이기 때문에 제어자를 생략할 수 있고, 생략된 제어자는 컴파일러가 자동적으로 추가해준다.
interface Movable {
void move(int x, int y);
}
interface Attackable {
void attack(Unit u);
}
interface Fightable extends Movable, Attackable { }
클래스의 상속과 마찬가지로 상위 인터페이스에 정의된 멤버들을 모두 상속받는다.
인터페이스는 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없다.
인터페이스에 정의된 메소드를 구현해주는 클래스를 작성해서 인스턴스를 생성해야 한다.
추상 클래스를 상속할 때는 extends라는 키워드를 사용했지만,
인터페이스는 구현한다는 의미에서 implements를 사용한다.
class Fighter implements Fightable {
public void move(int x, int y) { // 캐릭터 이동 }
public void attack(Unit u) { // 기본 공격하기 }
}
인터페이스의 모든 메소드를 구현해야 인스턴스가 완전한 클래스가 된다.
만약, 메소드 중 일부만 구현한다면 추상클래스로 선언해야 한다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { // 캐릭터 이동 }
}
또한 상속과 구현을 동시에 할 수도 있다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { // 캐릭터 이동 }
public void attack(Unit u) { // 기본 공격하기 }
}
자식 클래스의 인스턴스를 조상 클래스의 참조변수가 참조하는 다형성을 배웠다.
인터페이스 역시 이를 구현한 자식 클래스의 조상이기 때문에 다형성을 적용할 수 있다.
Fightable f = new Fighter();
void attack(Fightable f) {
// ...
}
인터페이스 타입의 매개변수가 갖는 의미는 메소드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 넘겨주어야 한다는 것이다.
Fightable method() {
...
Fighter f = new Fighter();
return f;
}
리턴 타입이 인터페이스라는 것은 인터페이스를 구현한 클래스의 인스턴스를 반환해야 한다는 것을 의미한다.
원래 인터페이스의 메소드는 구현부가 없지만 Java 8 부터
default 메소드와 static 메소드를 통해 구현 메소드를 정의할 수 있게 되었다.
이러한 점 때문에 오히려 인터페이스와 추상 클래스의 차이가 흐려졌다고 말하기도 한다.
조상 클래스에 메소드를 새로 추가하는 것은 별 일이 아니지만,
인터페이스의 경우라면 이야기가 많이 달라진다.
인터페이스의 메소드는 무조건 추상메소드이기 때문에 이를 구현받은 모든 클래스에서 일일이 새로 정의된 메소드를 다 구현해야되기 때문이다.
이런 점 때문에 추상 메소드의 기본적인 구현을 제공하는 디폴트 메소드가 만들어졌다.
default 키워드를 붙이며 일반 메소드처럼 구현부가 있어야 함public이어야 하며 생략 가능interface MyInterface {
void method();
void newMethod(); // 새로 추가한 메소드
}
newMethod()를 중간에 새롭게 추가하게 되면 이미 구현하고 있는 클래스들에서 직접 구현을 해주어야 한다.
interface MyInterface {
void method();
default void newMethod() { } // default 메소드
}
그러나 이렇게 default 메소드를 사용하면 구현하지 않아도 된다.
클래스는 다중 상속을 허용하지 않는다고 했다.
그 이유가 상위 클래스들에 동일한 메소드가 있을 때 처리 과정에서 문제가 발생할 수 있기 때문이었다. (죽음의 다이아몬드 현상)
그런데 인터페이스에서도 default 메소드가 추가되면서 다중 구현할 때 위와 같은 문제가 발생해버린다.
interface A {
public void methodA();
default void methodSame() { }
}
interface B {
public void methodB();
default void methodSame() { }
}
class MyClass implements A, B {
@Override
public void methodA() { }
@Override
public void methodB() { }
@Override
public void methodSame() { } // 충돌된 디폴트 메소드 오버라이딩
}
interface A {
public void methodA();
default void methodSame() { }
}
abstract class B {
abstract public void methodB();
public void methodSame() { }
}
class MyClass extends B implements A {
@Override
public void methodA() { }
@Override
public void methodB() { }
}
이 경우에는 2가지의 방법이 있다.
클래스의 상위 클래스를 가리키는 super 키워드를 인터페이스에서도 사용할 수 있다.
다만 문법이 약간 차이가 있다.
인터페이스를 호출하기 위해서는 인터페이스명.super.디폴트 메소드로 써야한다.
바로 위, 인터페이스의 디폴트 메소드와 부모 클래스의 메소드 간의 충돌 문제에서 인터페이스의 메소드를 구현하고 싶을 때 사용할 수 있다.
class MyClass extends B implements A {
@Override
public void methodA() { }
@Override
public void methodB() { }
@Override
public void methodSame() {
A.super.methodSame(); // super를 이용해 인터페이스 메소드 호출
}
}
static 메소드는 인스턴스와 관계가 없는 독립적인 메소드이기 때문에 인터페이스에 추가하지 못할 이유가 없었다.
그러나 인터페이스의 모든 메소드는 추상 메소드이어야 한다는 규칙에 예외를 두지 않기 위해서 만들지 않았다가 Java 8 에서 추가되었다.
대표적인 예시가 java.util.Collection 인터페이스가 있는데, 추상 메소드만 있어야 한다는 규칙 때문에 static 메소드를 따로 모아 놓은 java.util.Collections 클래스가 있다.
인터페이스 static 메소드라고 해서 특별한 것은 없다.
클래스의 static 메소드와 동일하게 취급한다.