열번째날 [인터페이스]
목차
1. 인터페이스의 역할
2. 인터페이스 선언
3. 인터페이스 구현
4. 인터페이스 사용
5. 타입 변환과 다형성
6. 인터페이스 상속
7. 디폴트 메소드와 인터페이스 확장
[인터페이스] like 음식점 메뉴판
인터페이스 안에는 추상 메소드 밖에 없음. 애초에 키워드를 안넣어도 됨
추상 클래스 위에 인터페이스가 있음.
인터페이스는 기능이 없음 인터페이스는 구현. 다형성 작업 가능
전체적인 틀을 잡는 느낌
1. 인터페이스의 역할
* 인터페이스란?
- 개발 코드와 객체가 서로 통신하는 접점 : 개발 코드는 인터페이스의 메소드만 알고 있으면 ok
(음식점에서 주방과 통신하는 것은 '메뉴판' = 인터페이스)
* 인터페이스 역할
- 개발 코드가 객체에 종속되지 않게 : 객체가 교체할 수 있도록 하는 역할
- 코드 변경 없이 리턴값 또는 실행 내용이 다양해 질 수 있음 : 다형성
2. 인터페이스 선언
* 인터페이스 선언
- 인터페이스 이름 : 규칙에 따라
- 소스 파일 생성 : 이름과 대소문자가 동일한 소스파일 생성
[public] interface 인터페이스명 {...}
* 구성 멤버 : 상수, 추상 메소드, 디폴트 메소드, 정적 메소드
1) 상수 필드 선언 : 상수필드 static final이 붙은 것
- 인터페이스는 상수 필드만 선언 가능
- 인터페이스에 선언된 필드는 모두 public static final
- 상수명은 대문자로 작성
- 선언과 동시에 초기값 지정 : static{} 블록 작성 불가 , static{} 으로 초기화 불가
int MIN_VOLUME = 0;
2) 추상 메소드 선언 : abstract method
- 인터페이스를 통해 호출된 메소드는 최종적으로 클래스가 가지고 있는 객체에서 실행
: 기본적으로 실행 블록이 없는 추상 메소드로 선언 : 틀만 잡는다
: public abstract을 생략해도 자동적으로 컴파일 과정에서 붙게 됨
void setVolume(int volume);
3) 디폴트 메소드 선언 : 추상 메소드와 구분하기 위해 default를 붙임 : default method
- default 키워드를 반드시 붙여야 함
- 실행 블록을 가지고 있는 메소드
- public 생략해도 자동으로 붙음
default void setMute(boolean mute){
if(mute){
System.out.println("무음 처리합니다.");
} else{
System.out.println("무음 해제합니다.");
}
}
4) 정적 메소드 선언 : static method
- 클래스의 정적메소드와 완전 동일함
- public 생략 가능
static void changeBattery(){
System.out.println("건전지를 교체합니다.");
}
3. 인터페이스 구현
* 인터페이스는 자체적으로 사용할 수 X : 추상 메소드 때문에 (완성되지 않았기 때문에)
* 클래스를 만들어서 인터페이스를 구현해야한다.
* 추상 메소드를 오버라이딩 해야한다 : 아무런 내용이 없기 때문에 새로 정의해야 한다.
* 구현 클래스를 쓰려면, 당연히 구현 객체를 생성해야 한다.
* 구현 클래스 선언 : implements 키워드
* 오버라이딩을 하려면, 부모클래스.인터페이스가 가지고 있는 것보다 더 넓은 범위로 접근제한자를 할 수 있으나,
좁은 범위로 접근제한자로 할 수 없다.
* 인터페이스의 모든 추상 메소드를 재정의하는 실체 메소드를 작성해야 한다.
- 일부만 재정의한다면, 클래스를 추상 클래스로 선언하고 메소드는 abstract 키워드 붙이면 된다.
* 익명 구현 객체
- 일회성의 구현 객체를 만들기 위해 소스 파일을 만들지 않고, 구현 객체를 만들 수 있는 방법
- 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야 한다.
- 추가적으로 필드와 메소드 선언이 가능하지만, 익명 객체 안에서만 사용가능하다.
- 인터페이스를 구현한 클래스의 이름이 없다
- 안드로이드, 모바일에서 많이 나옴
4. 인터페이스 사용
인터페이스 변수;
변수 = 구현객체;
또는
인터페이스 변수 = 구현객체;
RemoteControl rc;
rc = new Television();
* 추상 메소드 사용
RemoteControl rc = new Television();
rc.turnOn(); //Television의 turnOn() 실행
rc.turnOff(); //Television turnOff() 실행
* 디폴트 메소드 사용
- 인터페이스만으로는 사용 불가 : 구현 객체가 인터페이스에 대입되어야 호출할 수 있는 인스턴스 메소드
- 모든 구현 객체가 가지고 있는 기본 메소드라고 생각하기: 필요에 따라 구현 클래스가 디폴트 메소드 재정의해 사용
- RemoteControl 인터페이스의 setMute() 디폴트 메소드를 호출하는 방법
setMute() 메소드를 호출하려면 RemoteControl의 구현 객체가 필요
➡️ Television 객체를 인터페이스 변수에 대입한 후 setMute() 호출 가능
✅ 비록 setMute()가 Television에 선언되지 않았지만, Television 객체가 없다면 setMute()도 호출 불가능
RemoteControl rc = new Television();
rc.setMute(true);
* 정적 메소드 사용
- 인터페이스로 바로 호출 가능
public class RemotecontrolExample{
public static void main(String[] args){
RemoteControl.changeBattery();
}
}
5. 타입 변환과 다형성
* 다형성 : 하나의 타입에 여러 가직 객체 대입해 다양한 실행 결과를 얻는 것
* 다형성을 구현하는 기술
- 상속 또는 인터페이스의 자동 타입 변환
- 오버라이딩
* 다형성의 효과
- 다양한 실행 결과 얻을 수 있음
- 객체 부품화해서 유지보수 용이
* 자동 타입 변환 : 프로그램 실행 도중에 자동으로 타입 변환이 일어나는 것
- 구현 객체가 인터페이스 타입으로 변환
- 인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면, 자식 객체도 인터페이스로 자동 타입 변환 가능
* 필드의 다형성
인터페이스
public void roll(); //roll()메소드 호출 방법 설명
구현 클래스(1)
@Override
public void roll(){
System.out.println("한국 타이어가 굴러갑니다.") //인터페이스 구현
}
구현 클래스(2)
@Override
public void roll(){
System.out.println("금호 타이어가 굴러갑니다.")
}
필드의 다형성
public class Car {
//Tire 부분을 한국 타이어로 만들면, 나중에 금호타이어로 못만듦.
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
필드 다형성 테스트
public static void main(String[] args){
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
myCar.run();
- 필드 타입으로 타이어 인터페이스를 선언하게 되면 필드값으로 한국 타이어나 금호타이어 객체를 대입할 수 있다(자동 타입 변환)
- Car 객체를 생성한 후, 초기값으로 대입한 구현 객체 대신 다른 구현 객체를 대입할 수도 있다 : 타이어 교체
- 앞바퀴에 어떠한 타이어 구현 객체가 저장되어도 Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 전혀 문제X
- Car의 run() 메소드 수정 없이도 다양한 roll() 메소드의 실행 결과를 얻을 수 있음 ➡️ 필드의 다형성!
* 인터페이스 배열로 구현 객체 관리
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
}
- tires[0], tires[1], tires[2], tires[3]과 같이 인덱스로 표현되어 대입이나 제어문에서 활용하기 쉬워짐
* 매개변수의 타입이 인터페이스 인 경우
- 어떠한 구현 객체도 매개값으로 사용 가능하며, 객체에 따라 실행결과가 달라진다
- 구현 객체에 따라 메소드 실행 결과가 달라짐
* 강제 타입 변환(Casting)
- 구현 클래스 ➡️ 인터페이스 자동 타입 변환 후 / 인터페이스 ➡️ 구현 클래스로 강제 타입 변환하는 것
즉 ,구현 ➡️ 인터페이스 ➡️ 다시 구현
- 필요한 이유? 구현 클래스 타입에 선언된 다른 멤버 사용하기 위해서
구현클래스 변수 = (구현클래스) 인터페이스변수;
* 객체 타입 확인 : instanceof
- 어떤 구현 객체가 변환되어 있는지 알 수 없는 상태에서 무작정 강제 타입 변환을 하면, 예외 발생 할 수 있음!
- 인터페이스로 변환된 객체가 이게 맞는지 확인하기 위해서 사용
인터페이스변수 instanceof 구현클래스
6. 인터페이스 상속
* 인터페이스 간 상속 가능 : 다중 상속 허용
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 {...}
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
- 하위 인터페이스의 구현 클래스는 추상 메소드 모두 재정의해야 함
7. 디플트 메소드와 인터페이스 확장
* 디폴트 메소드가 있는 인터페이스 상속 : 부모 인터페이스의 디폴트 메소드를 자식 인터페이스에서 활용 방법
- 디폴트 메소드를 단순히 상속만 받음
- 디폴트 메소드를 재정의해서 실행 내용 변경
- 디폴트 메소드를 추상 메소드로 재선언