[JAVA] 인터페이스와 추상 클래스 (자바 독학하기19)
인터페이스의 기본적인 모습은 다음과 같다.
interface Printable {
public void print(String doc); // 추상 메소드
}
클래스 대신에 interface 를 붙이며 메소드는 몸체가 없이 바로 세미콜론이 붙으며 끝난다.
이와 같이 메소드의 몸체가 비어있는 메소드를 추상 메소드(Abstract Methods) 라 부른다.
인터페이스를 대상으로는 인스턴스 생성이 불가능 하다.
단지 다른클래스에 의해 상속이 될 뿐이며 extends 대신에 implements 가 사용된다.
인터페이스의 상속은 다음과 같다.
class Printer implements Printable {
public void print(String doc) {
System.out.println(doc);
}
}
이와 같은 관계 때문에 인터페이스를 상속하는 행위는 상속이 아닌 구현(implementaion) 이라고 한다.
클래스의 인터페이스 구현에는 다음과 같은 특징이 있다.
- 구현할 인터페이스를 명시할 때 키워드 implements 를 사용한다.
- 한 클래스는 둘 이상의 인터페이스를 동시에 구현할 수 있다.
- 상속과 구현은 동시에 가능하다.
또한 인터페이스는 다음과 같은 특징이 있다.
- 인터페이스의 형을 대상으로 참조변수의 선언이 가능하다.
- 인터페이스의 추상 메소드와 이를 구현하는 메소드 사이에 오버라이딩 관계가 성립하며 이에 따라 어노테이션 @Override 의 선언이 가능하다.
인터페이스의 사전적 의미는 연결점 또는 접점이으로 둘 사이를 연결하는 매개체이다. 자바에서도 이와 같은 역할을 하고 있다.
위 코드를 보면 OS 입장에서는 프린트 하는 동작에 대해 여러 업체의 프린터를 사용하기 위해
interface 를 제공하면서, 해당 interface 에 맞게 프린터가 동작하는 코드를 구현하여 드라이버를 만들어 오도록 한다.
그러면 자신들의 OS 는 interface 내의 추상 메소드로 제공한 메소드만 호출하여 각 프린터에 맞춰 프린트를 할 수 있다.
이러한 동작을 위해 interface 를 사용하는 예를 나타낸 것이다.
이러한 추상 메소드는 다음과 같은 특징이 있다.
- 인터페이스의 모든 메소드는 public 이 선언된 것으로 간주한다.
따라서 위의 코드에서 추상 메소드를 void print (String doc); 으로 구현하여도 무방하다.
인터페이스 내에서 변수 선언도 가능한데 이렇게 선언된 변수는 다음의 특징이 있다.
- 반드시 서언과 동시에 값으로 초기화를 해야 한다.
- 모든 변수는 public, static, final 이 선언된 것으로 간주한다.
그리고 인터페이스를 구현하는 클래스는 인터페이스에 존재하는 모든 추상메소드를 구현하여야 한다.
하나라도 구형되어 있지 않다면 해당 클래스를 대상으로 인스턴스 생성이 불가능하다.
위에서 구현된 코드에서 삼성프린터가 새로운 모델을 출시한다고 하면 인터페이스 내에 새로운 추상 메소드를 구현할 수는 없다. 기존 인터페이스를 수정한다면 삼성프린터 뿐만 아니라 다른 모든 프린터 회사들이 새로운 추상 메소드를 구현해야 하기 때문이다.
이런 상황을 위해 자바에선 인터페이스의 상속을 지원하는데
interface Printable {
void print(String doc);
}
interface ChangePrintable extends Printable {
void changeprint(String doc);
}
와 같이 사용한다.
- 두 클래스 사이의 상속은 extends 로 명시한다.
- 두 인터페이스 사이의 상속도 extends 로 명시한다.
- 인터페이스와 클래스 사이의 구현만 implements 로 명시한다.
만약 기능 보강을 위해 인터페이스에 새로운 추상 메소드를 추가 할 수 밖에 없는 상황이라면 추상 메소드를 사용해 모든 클래스에서 추가되는 메소드를 구현해야하는 불편함이 있다.
이를 방지하기 위해 자바 8에서 디폴트 메소드(Default Methods) 라는 것이 소개 되었다.
위의 인터페이스 상속의 내용을 디폴트 메소드로 구현하면
interface Printable {
void print(String doc); //추상 메소드
default void changeprint(String doc) { } //디폴트 메소드
}
이 디폴트 메소드는 다음의 특징이 있다.
- 자체로 완전한 메소드이다.
- 이를 구현하는 클래스가 오버라이딩 하지 않아도 된다.
이는 이전에 개발해 놓은 코드에 영향을 미치지 않기 위해 등장한 문법이다.
인터페이스 내부에는 static 메소드가 구현될 수 있다.
이는 추상 메소드처럼 몸체가 없는 메소드가 아닌 내용이 있을 수 있는 메소드이다.
기존의 static 메소드 처럼 static void a { ... } 와 같이 구현하며 클래스에서의 호출 방법도 같다.
이 static 메소드는 개발자가 직접 정의하는 일은 드물기 때문에 존재만 알아두면 된다.
인터페이스에서도 instanceof 연산이 가능하며 상속에서 동작한 것과 똑같이 동작한다.
인터페이스는 클래스에 특별한 표식을 다는 용도로도 사용이 된다.
이러한 용도의 인터페이스를 마커 인터페이스(Marker Interface) 라고 한다.
마커 인터페이스에는 아무런 메소드도 존재하지 않는 경우가 많다.
책에서는 interface Upper {} ,interface Lower {} 로 선언 후에
interface Printable {
public void print(String doc); // 추상 메소드
}
class Printer implements Printable, Upper {
public void print(String doc) {
System.out.println(doc);
}
}
와 같이 2개의 인터페이스를 상속한다.
이때 Upper 인터페이스는 추상메소드가 존재하지 않기 때문에 구현이 필요없고
intanceof 를 통해 Upper 를 상속하는지 Lower 를 상속하는지 체크 하고 이에 따른 처리를 구분한다.
이러한 방식으로 해당 클래스에 마커를 다는 것이다.
하나 이상의 추상 메소드를 갖는 클래스를 가리켜 추상 클래스(Abstract Class) 라고 한다.
이러한 클래스는 정의할때 선언부에 abstract 를 추가하여야 한다.
하나 이상의 추상 메소드를 갖고 있으므로 이 메소드는 다른 클래스에 의해 추상 클래스가 구현되어야 한다.
상속시에 사용하는 키워드는 extends 이다.