8.1 인터페이스의 역할
인터페이스(interface)는 객체의 사용 방법을 정의한 타입이다.
인터페이스는 객체의 교환성을 높여주기 때문에 다형성을 구현하는 매우 중요한 역할을 한다.
특히 자바 8에서 인터페이스의 중요성은 더 커졌다.
자바8의 람다식은 함수적 인터페이스의 구현 객체를 생성하기 때문이다.
인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출 시킨다.
그렇기 때문에 개발 코드는 객체 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.
- 인터페이스의 역할
• 개발 코드가 객체에 종속되지 않게 -> 객체 교체할 수 있도록 하는 역할
• 개발 코드 변경 없이 리턴값 또는 실행 내용이 다양해 질 수 있음 (다형성)
8.2 인터페이스 선언
인터페이스는 .java형태의 소스 파일로 작성되고 컴파일러(javac.exe)를 통해 .class 파일로 컴파일 되기 때문에 물리적으로는 클래스와 동일하다.
8.2.1 인터페이스 선언
인터페이스 선언은 class 키워드 대신에 interface 키워드를 사용한다.
[public] interface 인터페이스명 {.....}
인터페이스 이름은 클래스 이름을 작성하는 방법과 동일하다.
영어 대소문자를 구분하며, 첫 문자를 대문자로 하고 나머지는 소문자로 작성하는 것이 관례이다.
public 접근 제한은 다른 패키지에서도 인터페이스를 사용 할 수 있도록 해준다.
클래스는 필드, 생성자, 메소드를 구성 멤버로 가지는데 비해, 인터페이슨느 상수와 메소드만을 구성 멤버로 가진다.
인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 업다.
자바 7 이전 까지는 인터페이스의 메소드는 실행 블록이 없는 추상 메소드로만 선언이 가능 했지만, 자바 8부터는 디폴트 메소드와 정적 메소드도 선언이 가능하다.
interface 인터페이스명{
//상수
타입 상수명 = 값;
//추상 메소드
타입 메소드명(매개변수,....);
//디폴트 메소드
default 타입 메소드명(매개변수,....){....}
//정적 메소드
static 타입 메소드명(매개변수){....}
}
8.2.2 상수 필드(Constant Field) 선언
• 인터페이스는 상수 필드만 선언 가능
- 데이터 저장하지 않음
• 인터페이스에 선언된 필드는 모두 public static final
- 자동적으로 컴파일 과정에서 붙음
• 상수명은 대문자로 작성
- 서로 다른 단어로 구성되어 있을 경우에는 언더 바(_)로 연결
• 선언과 동시에 초기값 지정
- static { } 블록 작성 불가 - static {} 으로 초기화 불가
public interface RemoteControl{
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
}
8.2.3 추상 메소드 선언
• 인터페이스 통해 호출된 메소드는 최종적으로 객체에서 실행
- 인터페이스의 메소드는 기본적으로 실행 블록이 없는 추상 메소드로 선언
- public abstract를 생략하더라도 자동적으로 컴파일 과정에서 붙게 됨
public interface RemoteControl{
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
//추상 메소드
public void turnOn();
public void turnOff();
public void setVolume(int volume);
}
8.2.4 디폴트 메소드 선언
• 자바8에서 추가된 인터페이스의 새로운 멤버
[public] default 리턴타입 메소드명(매개변수, ...){..코드블럭..}
• 실행 블록을 가지고 있는 메소드
• default 키워드를 반드시 붙여야
• 기본적으로 public 접근 제한
- 생략하더라도 컴파일 과정에서 자동 붙음
public interface RemoteControl{
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
//추상 메소드
public void turnOn();
public void turnOff();
public void setVolume(int volume);
//디폴트 메소드
default void setMute(boolean mute){
if(mute){
System.out.println("무음 처리 합니다.");
}else{
System.out.println("무음 해제 합니다.");
}
}
}
8.2.5 정적 메소드 선언
• 자바8에서 추가된 인터페이스의 새로운 멤버
• 디폴트 메소드와는 달리 객체가 없어도 인터페이스만으로 호출이 가능하다.
[public] 리턴타입 메소드명(매개변수,...){.....}
public interface RemoteControl{
static void changeBattery(){
System.out.println("건전지를 교환합니다.");
}
}
8.3 인터페이스 구현
■ 구현 객체와 구현 클래스
- 인터페이스의 추상 메소드 대한 실체 메소드를 가진 객체 = 구현 객체
- 구현 객체를 생성하는 클래스 = 구현 클래스
개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.
객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 한다.
이러한 객체를 인터페이스의 구현(implement)객체 라고 한다.
구현 객체를 생성하는 클래스를 구현 클래스라고 한다.
8.3.1 구현 클래스
■ 구현 클래스 선언
- 자신의 객체가 인터페이스 타입으로 사용할 수 있음
• implements 키워드로 명시
public class 구현클래스명 implements 인터페이스명{
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
구현 클래스는 보통의 클래스와 동일한데, 인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스명을 명시해야 한다.
그리고 인터페이스에 선언된 추상 메소드의 실체 메소드를 선언해야 한다.
■ 추상 메소드의 실체 메소드를 작성하는 방법
- 메소드의 선언부가 정확히 일치해야
- 인터페이스의 모든 추상 메소드를 재정의하는 실체 메소드 작성해야
• 일부만 재정의할 경우, 추상 클래스로 선언 + abstract 키워드 붙임
8.3.2 익명 구현 객체
- 명시적인 구현 클래스 작성 생략하고 바로 구현 객체를 얻는 방법
• 이름 없는 구현 클래스 선언과 동시에 객체 생성
• 인터페이스의 추상 메소드들을 모두 재정의하는 실체 메소드가 있어야
• 추가적으로 필드와 메소드 선언 가능하나 익명 객체 안에서만 사용
• 인터페이스 변수로 접근 불가
구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용 할 수 있기 떄문에 편리하지만, 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고, 클래스를 선언하는 것은 비효율적이다.
자바는 소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법을 제공하는데, 그것이 익명구현객체이다.
public lcass Remote ControllExample{
public static void main(String[] args){
RemoteControl rc = new RemoteControl(){
public void turnOn(){....}
public void turnOff(){....}
puboic void setVolume(int vomume){....}
};
}
}
new 연산자 뒤에는 클래스 이름이 와야 하는데, 이름이 없다.
인터페이스() { } 는 인터페이스를 구현해서 중괄화 { } 와 같이 클래스를 선언하라는 뜻이고, new 연산자는 이렇게 선언된 클래스를 객체로 생성한다.
중괄호 { } 에는 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야 한다.
그렇치 않으면 컴파일 에러가 발생한다.
추가적으로 필드와 메소드를 선언할 수 있지만, 익명 객체 안에서만 사용할 수 있고, 인터페이스 변수로 접근할 수 없다.
모든 객체는 클래스로부터 생성되는데, 익명 구현 객체도 예외는 아니다.
RemoteControllerExample.java를 컴파일 하면 자바 컴파일러에 의해 자동으로 다음과 같은 클래스 파일이 만들어진다.
"RemoteControllerExample.class" 이름 뒤에 $가 붙고 생성 번호가 붙는데 생성번호는 1부터 시작한다.
만약 두번째 익명 구현 객체를 만들었다면 "RemoteControllerExample$2.class"가 된다.
8.3.3 다중 인터페이스 구현 클래스
8.4 인터페이스 사용
인터페이스로 구현 객체를 사용하려면 다음과 같이 인터페이스 변수를 선언하고 구현 객체를 대입해야 한다.
인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.
예를들어 RemoteControl 인터페이스로 구현 객체인 Television과 Audio를 사용하려면 다음과 같이 RemoteControl 타입 변수 rc를 선언하고 구현 객체를 대입해야 한다.
RemoteControl rc;
rc = new Television();
rc = new Audio();
개발 코드에서 인터페이스는 클래스의 필드, 생성자 또는 메소드의 매개변수, 생성자 또는 메소드의 로컬 변수로 선언 될 수 있다.
8.4.1 추상 메소드 사용
구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 호출 할 수 있게 된다.
개발 코드에서 RemoteControl 변수 rc로 turnOn()또는 turnOff() 메소드를 호출하면 구현 객체의 turnOn()과 turnOff() 메소드가 자동 실행 된다.
RemoteControl rc = new Television();
rc.turnOn(); //Television의 turnOn()실행
rc.turnOff(); //Television의 turnOff()실행
8.4.2 디폴트 메소드 사용
- 인터페이스만으로는 사용 불가
• 구현 객체가 인터페이스에 대입되어야 호출할 수 있는 인스턴스 메소드
- 모든 구현 객체가 가지고 있는 기본 메소드로 사용
• 필요에 따라 구현 클래스가 디폴트 메소드 재정의해 사용
디폴트 메소드는 인터페이스에 선언되지만, 인터페이스에서 바로 사용할 수 없다.
디폴트 메소드는 추상 메소드가 아닌 인스턴스 메소드이므로 구현 객체가 있어야 사용할 수 있다.
예를들어 RemoteControl 인터페이스는 setMute()라는 디폴트 메소드를 가지고 있지만, 이 메소드를 다음과 같이 호출할 수는 없다.
RemoteControl.setMute(true); //호출 불가
setMute() 메소드를 호출하려면 RemoteControl의 구현 객체가 필요한다ㅔ, 다음과 같이 Television 객체를 인터페이스 변수에 대입하고 나서 setMute()를 호출할 수 있따.
비록 setMute()가 Television에 선언되지는 않았지만 Television 객체가 없다면 setMute()도 호출 할 수 없다.
RemoteControl rc = new Television();
rc.setMute(true);
디폴트 메소드는 인터페이스의 모든 구현 객체가 가지고 있는 기본 메소드라고 생각하면 된다.
그러나 어떤 구현 객체는 디폴트 메소드의 내용이 맞지 않아 수정이 필요할 수도 있다.
구현 클래스를 작성할 때 디폴트 메소드를 재정의(오버라이딩)해서 자신에 맞게 수정하면 디폴트 메소드가 호출 될 때 자신을 재정의한 메소드가 호출된다.
8.4.3 정적 메소드 사용
인터페이스의 정적 메소드는 인터페이스로 바로 호출이 가능하다.
public class RemoteControlExample{
public static void main(){
RemoteControl.changeBattery();
}
}
8.5 타입 변환과 다형성
- 하나의 타입에 여러 가지 객체 대입해 다양한 실행 결과를 얻는 것
- 다형성을 구현하는 기술
• 상속 또는 인터페이스의 자동 타입 변환(Promotion)
• 오버라이딩(Overriding)
- 다형성의 효과
• 다양한 실행 결과를 얻을 수 있음
• 객체를 부품화시킬 수 있어 유지보수 용이 (메소드의 매개변수로 사용)
8.5.1 자동 타입 변환(Promotion)
구현 객체가 인터페이스 타입으로 변환된느 걳은 자동 타입 변환(Promotion)에 해당한다.
자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 변환 시킬 수 있다.
8.5.2 필드의 다형성
다음 그림은 상속에서 다형성을 설명할 때 보여준 그림과 유사하다.
상속에서는 타이어 클래스 타입에 한국 타이어와 금호 타이어라는 자식 객체를 대입해서 교체할 수 있음을 보여주었지만, 아래 그림은 타이어가 클래스 타입이 아니고 인터페이스라는 점과 한국 타이어와 금호 타이어는 자식 클래스가 아니라 구현 클래스라는 점이다.
한국 타이어와 금호 타이어는 공통적으로 타이어 인터페이스를 구현했기 떄문에 모두 타이어 인터페이스에 있는 메소드르 가지고 있다.
따라서 타이어 인터페이스로 동일하게 사용할 수 있는 교체 가능한 객체에 해당한다.
8.5.3 인터페이스 배열로 구현 객체 관리
이전 예제에서는 Car 클래스에서 4개의 타이어 필드를 인터페이스로 각각 선언했지만 다음과 같이 인터펭시ㅡ 배열로 관리할 수 있다.
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
8.5.4 매개 변수의 다형성
- 매개 변수의 타입이 인터페이스인 경우
• 어떠한 구현 객체도 매개값으로 사용 가능
• 구현 객체에 따라 메소드 실행결과 달라짐
8.5.5 강제 타입 변환(Casting)
- 인터페이스 타입으로 자동 타입 변환 후, 구현 클래스 타입으로 변환
• 필요성: 구현 클래스 타입에 선언된 다른 멤버 사용하기 위해
8.5.6 객체 타입 확인(instanceof)
if( vehicle instanceof Bus){
Bus bus = (Bus)vehicle;
}
8.6 인터페이스 상속
인터페이스도 다른 인터페이스를 상속 할 수 있다.
인터페이스는 클래스와는 달리 다중 상속을 허용한다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2{.....}
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드 뿐만 아니라 상위 인터펭시ㅡ의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다.
그렇기 때문에 구현 클래스로부터 객체를 생성하고 나서 다음과 같이 하위 및 상위 인터페이스 타입으로 변환이 가능하다.
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
8.7 디폴트 메소드와 인터페이스 확장
디폴트 메소드는 인터페이스에 선언도니 인스턴스 메소드이기 떄문에 구현 객체가 있어야 사용할 수 있따.
선언은 인터페이스에서 하고, 사용은 구현 객체를 통해 한다는 것이 어색해 보인다.
디폴트 메소드는 모든 구현 객체에서 공유하는 기본 메소드처럼 보이지만, 사실은 인터페이스에서 디폴트 메소드를 허용한 다른 이유가 있다.
8.7.1 디폴트 메소드의 필요성
인터페이스에서 디폴트 메소드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다.
기존 인터페이스의 이름과 추상 메소드의 변경 없이 디폴트 메소드만 추가할 수 있기 떄문에 이전에 개발한 구현 클래스를 그대로 사용할 수 있으면서 새롭게 개발하는 클래스는 디폴트 메소드를 활용할 수 있다.
다음 그림을 보면서 이해해 보자
기존에 MyInterface라는 인터페이스와 이를 구현한 MyClassA라는 클래스가 있었다.
시간이 흘러 MyInterface에 기능을 추가해야 할 필요성이 생겼다.
그래서 MyInterface에 추상 메소드를 추가했는데, 엉뚱하게도 MyClassA에서 문제가 발생한다.
그 이유는 추가된 추상 메소드에 대한 실체 메소드가 MyClassA에 없기 때문이다.
MyClassA를 수정할 여건이 안된다면 결국 MyInterface에 추상 메소드를 추가할 수 없다.
그래서 MyInterface에 디폴트 메소드를 선언한다.
디폴트 메소드는 추상 메소드가 아니기 때문에 구현 클래스에서 실체 메소드를 작성할 필요가 없다.
따라서 MyClassA는 아무런 문제 없이 계속 사용이 가능하다.
8.7.2 디폴트 메소드가 있는 인터페이스 상속
인터페이스 간에도 상속이 있다는 것을 이미 학습했었다.
부모 인터페이스에 디폴트 메소드가 정의되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 다음 3가지가 있다.
- 부모 인터페이스의 디폴트 메소드를 자식 인터페이스에서 활용 방법
• 디폴트 메소드를 단순히 상속만 받음
• 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경
• 디폴트 메소드를 추상 메소드로 재선언
'JAVA > 이것이 자바다' 카테고리의 다른 글
Chapter .10 예외처리 (0) | 2021.07.22 |
---|---|
Chapter .09 중첩 클래스와 중첩 인터페이스 (0) | 2021.07.20 |
Chapter .07 상속 (0) | 2021.07.17 |
Chapter .06-2 클래스 (0) | 2021.07.16 |
Chapter .06-1 클래스 (0) | 2021.07.14 |