인터페이스란? 추상 클래스처럼 추상 메서드를 갖지만 추상 클래스보다 추상화 정도가 높아서 일반 메서드(구현부가 구현된)또는 멤버변수(int x;등)를 구성원으로 가질 수 없다.
오직 추상 메서드와 상수만을 멤버로 가질 수 있다.
인터페이스도 추상 클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성한다.
인터페이스를 작성하는 방법은 클래스와 비슷하다. 추상 클래스처럼 앞에 무언가를 붙힐 필요는 없고 그저 class를 interface로 바꾸어 주면된다.
interface InterfaceName {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수목록);
}
일반적인 클래스와 달리 인터페이스의 멤버들은 다음과 같은 제약사항이 존재한다
- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
(단, static메서드와 디폴트 메서드는 예외)
interface PlayingCard {
public static final int S = 4;
final int DIAMOND = 3; // public static final int DIAMOND = 3;
static int HEART = 2; // public static final int HEART = 2;
int CLOVER = 1; // public static final int CLOVER = 1;
public abstract String getCardNumber();
String getCarKind(); // public abstract String getCarKind();
}
그저 타입과 상수이름, 반환타입과 메서드명만 적어 주어도 컴파일러에서 자동으로 public static final을 앞에 붙혀주기 때문에 생략이 가능하다.
인터페이스의 장점
- 개발시간을 단축시킬 수 있다.
- 표준화가 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수있다.
- 독립적인 프로그래밍이 가능하다.
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러개의 인터페이스로부터 상속을 받는 것이 가능하다.
interface Movable {
// 지정된 위치(x, y)로 이동하는 기능의 메서드
void move(int x, int y);
}
interface Attackable {
// 지정된 대상(u)을 공격하는 기능의 메서드
void attack(Unit u);
}
interface Fightable extends Movable, Attacable { }
클래스의 상속과 마찬가지로 자손 인터페이스(Fightable)은 조상 인터페이스에 정의된 멤버를 모두 상속받는다.
인터페이스도 추상 클래스처럼 그 자체로는 인스턴스 생성이 불가능하다. 인터페이스도 자신에 정의된 추상메서드를 구현할 클래스를 작성해야 하는데, 키워드만 'extends'에서 'implements'로 바뀐다.
class 클래스 이름 implements 인터페이스이름 { }
class Fighter implements Fightable {
public void move(int x, int y) { //생략 }
public void attack(Unit u) { //생략 }
}
만일 구현하는 인터페이스의 메서드 중 일부만 구현한다면, abstract를 붙여서 추상 클래스로 선언해야 한다.
abstract class Fighter implements Fightable {
public void move(int x, int y) { //생략 }
// attack()메서드를 구현하지 않음.
}
다음과 같이 상속과 구현을 동시에 할 수 있다.
class Fighter extends Unit implements Figtable {
public void move(int x, int y) { //생략 }
public void attac(Unit u) { //생략 }
}
상속을 이용한 클래스의 다형성에서 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능했었다. 인터페이스 역시 이를 구현한 클래스의 입장에서 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.
Fightable f = (Fightable)new Fighter();
Fightable f = new Fighter();
// 따라서 아래와 같이 메서드의 매개변수타입으로 인터페이스도 가능하다.
void attack(Fightable f) {
//...
}
인터페이스 타입의 매개변수가 갖는 의미는 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 뜻이다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { //생략 }
public void attack(Fightable f) { //생략 }
}
Fightable method() {
Fighter f = new Fighter();
return f;
}
위의 코드에서 볼 수 있듯, Fightable을 반환하는 메서드에 그 구현 클래스의 인스턴스를 반환하는것도 가능하다.
리턴 타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
위 코드에서는 Fightable 인터페이스를 구현한 클래스들 중 Fighter클래스의 인스턴스가 반환된다는 것을 의미한다.
원래는 인터페이스에 abstract 메서드만 선언할 수 있었지만 java8부터는 디폴트 메서드와 static메서드도 추가할 수 있게 되었다. 인터페이스의 경우 추상화의 끝판왕이라 볼 수 있기때문에 하나의 인터페이스에 메서드 한개가 추가될 경우 엄청나게 많은 클래스를 수정해야 할 수도 있다. 그래서 JDK 설계자들은 디폴트 메서드(default method)라는 것을 고안해내었다. 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 새로 추가되어도 해당 클래스에서 구현할 필요가 없다.
interface MyInterface {
void method();
}
//에서
interface MyInterface {
void method();
default void newMethod() {}
}
위의 인터페이스에서 아래와같이 default메서드로 newMethod()를 선언해 준다면 이 인터페이스를 구현했던 모든 클래스에서 해당 newMethod를 구현하지 않아도 된다. 조상클래스에서 새로운 메서드를 만든것과 동일하다.
대신, 디폴트 메서드가 구현해 놓았떤 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있는데, 다음과 같은 규칙으로 해결할 수 있다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
- 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.