인터페이스
인터페이스(interface)는 일종의 추상클래스이며 추상클래스보다 추상화 정도가 더 높습니다. 추상클래스를 미완성 설계도로 비유한다면 인터페이스는 기본 설계도에 비유할 수 있습니다. 인터페이스는 다른 클래스 작성에 도움을 주거나 서로 무관한 클래스들에게 관계를 맺어줄 목적으로 사용됩니다.
인터페이스의 특징을 정리하면 다음과 같습니다.
- class 키워드 대신 interface 키워드를 사용하여 정의
- 인스턴스 생성 불가
- 모든 멤버변수에는 public static final 제어자가 붙음 (생략 가능)
- 모든 메서드는 public abstract 제어자가 붙음 (생략 가능)
※ 제어자 생략시 컴파일러가 자동으로 추가.
1.1 인터페이스의 생성
다음과 같이 class 키워드 대신 interface 키워드를 사용하여 인터페이스를 생성합니다.
interface [인터페이스명] {
public static final 타입 상수명 = 값;
public abstract 메서드명(매개변수);
}
1.2 인터페이스의 상속
인터페이스는 인터페이스만 상속받을 수 있으며 추상클래스와는 달리 여러 개의 인터페이스에 대해 다중 상속이 가능합니다. 인터페이스의 상속에는 extends 키워드를 사용합니다.
다음 예제를 통해 인터페이스의 다중 상속에 대해 확인해보겠습니다.
interface Movable { void move(int x, int y); } interface Attackable { void attack(Unit unit); } interface Fightable extends Movable, Attackable { int x = 10; int y = 20; }
※ 인터페이스는 Object 클래스와 같은 최고 조상이 없음.
※ 인터페이스는 다중 상속을 위한 기능이 아니므로 자바에서 인터페이스로 다중 상속을 구현하는 경우는 거의 없음.
1.3 인터페이스의 구현
인터페이스는 추상클래스처럼 그 자체로 인스턴스 생성이 불가능합니다. 따라서 구현을 통해 추상메서드를 모두 구현하여 사용해야합니다.
다음과 같이 implements 키워드를 사용하여 인터페이스를 구현합니다.
class [클래스명] implements [인터페이스명] {
// 추상 메서드를 모두 구현.
}
※ 인터페이스의 추상 메서드를 모두 구현하지 않은 경우, 해당 클래스에 abstract 키워드를 붙여서 추상클래스로 만들어야 함.
다음 예제를 통해 인터페이스의 구현에 대해 확인해보겠습니다.
interface Fightable { void move(int x, int y); void attack(Unit unit); } class Fighter implements Fightable { public void move(int x, int y) { // 내용 } public void attack(Unit unit) { // 내용 } }
구현한 메서드에는 public 제어자를 붙여줬는데 인터페이스의 추상 메서드 제어자가 public abstract 이므로 이에 맞춰서 붙여준 것입니다.
또한 다음과 같이 상속과 구현을 동시에 하는 것도 가능합니다.
interface Movable { void move(int x, int y); } abstract class Attack { abstract public void attack(Unit unit); } class Fighter extends Attack implements Movable { public void move(int x, int y) { // 내용 } public void attack(Unit unit) { // 내용 } }
1.4 인터페이스의 다형성
이전에 알아본 다형성의 특징처럼 부모타입의 참조변수로 자식 클래스의 인스턴스를 참조하는 것이 인터페이스에서도 가능합니다. 인터페이스에서의 다형성이 갖는 특징을 정리하면 다음과 같습니다.
- 인터페이스 타입의 참조변수으로 구현한 클래스의 인스턴스 참조 가능
- 인터페이스 타입으로 형변환 가능
- 인터페이스에도 매개변수의 다형성 특징이 적용
- 메서드의 리턴타입으로 인터페이스 타입 지정 가능
다음과 같이 인터페이스 타입의 참조변수로 구현한 클래스의 인스턴스 참조가 가능합니다.
ex) Fightable fightable = (Fightable) new Fighter();
이 때, Fightable 타입의 참조변수로는 Fightable 인터페이스에 정의된 멤버만 호출이 가능합니다.
또한 다음과 같이 메서드의 리턴타입으로 인터페이스 타입을 지정하는 것이 가능합니다.
ex)
Fightable method() {
Fighter fighter = new Fighter();
return fighter;
}
위의 예시의 경우 메서드의 리턴타입이 인터페이스이기 때문에 return문에서 인터페이스를 구현한 클래스의 인스턴스를 반환하는 것을 확인할 수 있습니다.
※ 메서드의 리턴타입이 인터페이스인 경우, 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미.
1.5 인터페이스의 장점
인터페이스의 사용 이유와 장점을 정리하면 다음과 같습니다.
1. 개발시간 단축
- 메서드 내용과 관계없이 선언부만 알면 개발이 가능
- 양쪽에서 동시에 개발 진행 가능
2. 표준화 가능
- 기본 틀을 인터페이스로 작성
- 일관되고 정형화된 프로그램 개발이 가능
3. 서로 무관한 클래스들에게 관계를 맺어줌
- 하나의 인터페이스를 공통으로 구현하도록 하여 관계를 맺어줌
4. 독립적인 프로그래밍이 가능
- 인터페이스를 이용하면 클래스 간의 관계를 간접적으로 구성하는 것이 가능
- 한 클래스의 변경이 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능
1.6 인터페이스의 이해
인터페이스의 이해를 위해서는 다음 두가지 사항을 숙지하고 있어야 합니다.
- 클래스에 대해서 User(사용자)와 Provider(제공자)가 있음
- User(사용자)에서는 Provider(제공자) 메서드의 선언부만 알면 됨
클래스간의 관계에서 직접적인 관계의 경우 한쪽이 변경되면 다른 한쪽도 변경되어야 하는 단점을 가지고 있습니다. 그러나 인터페이스를 매개체로 한 간접적인 관계의 경우 한쪽이 변경되어도 다른 한쪽은 전혀 영향을 받지 않습니다.
다음 예제를 통해 인터페이스의 간접적 관계에 대해 확인해보겠습니다.
// 클래스 B의 메서드를 추상 메서드로 정의한 인터페이스 I interface I { public abstract void methodB(); } // 클래스 B가 인터페이스 I를 구현 class B implements I { public void methodB() { System.out.println("methodB in B class"); } } // 클래스 A는 인터페이스 I를 사용 가능 class A { public void methodA(I i) { i.methodB(); } }
'A-I-B'의 간접적인 관계로 구성되었으며 클래스 A는 인터페이스 I 하고만 직접적인 관계가 있기 때문에 클래스 B의 변경에 영향을 받지 않습니다.
클래스 A는 직접적인 관계의 인터페이스 I의 영향만 받기 때문에 실제로 사용되는 클래스명을 몰라도 되고 구현된 클래스가 존재하지 않아도 문제가 되지 않습니다.
인터페이스 I 는 클래스 B를 감싸고 있는 껍데기이며 클래스 A는 인터페이스 I 안에 어떤 클래스가 구현되어 있는지 몰라도 되는 관계가 됩니다.
1.6 디폴트 메서드와 static 메서드
인터페이스는 원래 상수와 추상메서드만을 멤버로 가졌었는데 JDK1.8 이후로 디폴트 메서드와 static 메서드도 추가할 수 있게 되었습니다.
static 메서드는 인스턴스와 무관한 독립적인 메서드이기 때문에 인터페이스에 추가 못할 이유가 없었지만, 자바의 규칙을 단순화하기 위해 인터페이스의 모든 메서드를 추상 메서드로 제한하여 제외되었습니다.
인터페이스의 static 메서드는 접근제어자도 마찬가지로 항상 public이며 생략이 가능합니다.
디폴트 메서드는 추상 메서드와 static 메서드 외에 인터페이스에 추가할 수 있는 메서드이며, 인터페이스에 추가해도 구현한 클래스의 변경없이 사용이 가능합니다.
다음과 같이 default 키워드를 앞에 붙여 사용합니다.
ex)
interface I {
public abstract void abstractMethod();
public default void defaultMethod() {}
}
디폴트 메서드 역시 접근제어자가 public 이며 생략이 가능합니다.
디폴트 메서드의 경우 기존의 메서드와 메서드명이 중복되는 경우가 발생하는데, 이에 대한 규칙은 다음과 같습니다.
1. 여러 인터페이스 구현시 디폴트 메서드명이 같은 경우
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 함
2. 부모 클래스의 메서드와 디폴트 메서드명이 같은 경우
- 부모 클래스의 메서드가 상속되고 디폴트 메서드는 무시됨
다음 예제를 통해 디폴트 메서드의 특징에 대해 확인해보겠습니다.
class DefaultMethodTest { public static void main(String[] args) { Child child = new Child(); child.method1(); child.method2(); Interface1.staticMethod(); Interface2.staticMethod(); } } class Parent { // 인터페이스 Inteface1의 디폴트 메서드와 메서드명이 같으므로 // 부모 클래스의 메서드가 상속됨. public void method2() { System.out.println("method2() in Parent"); } } class Child extends Parent implements Interface1, Interface2{ // 인터페이스 Interface1, Interface2의 디폴트 메서드명이 같으므로 // 아래와 같이 구현한 클래스에서 오버라이딩 해줌. public void method1() { System.out.println("method1() in Child"); } } interface Interface1 { default void method1() { System.out.println("method1() in Interface1"); } default void method2() { System.out.println("method2() in Interface1"); } static void staticMethod() { System.out.println("staticMethod() in Interface1"); } } interface Interface2 { default void method1() { System.out.println("method1() in Interface2"); } static void staticMethod() { System.out.println("staticMethod() in Interface2"); } }
위의 예제를 실행하면 다음과 같은 결과가 출력됩니다.
method1() in Child
method2() in Parent
staticMethod() in Interface1
staticMethod() in Interface2
이상으로 자바의 인터페이스에 대해서 알아봤습니다.
※ 참고 문헌
남궁성, 『Java의 정석 3rd Edition』, 도우출판(2016), p381 ~ p402. Chapter 07 객체지향 프로그래밍 II