개발자 도전기
[JAVA] 인터페이스(interface) 본문
인터페이스란?
인터페이스는 서로 다른 두 객체를 연결하는 역할을 한다. 예를 들어 객체 A는 인터페이스를 통해 객체 B를 사용할 수 있다. 추상 클래스와 유사하게 추상메소드를 가지기 때문에 생성자와 인스턴스를 생성할 수 없다.
인터페이스와 다형성
객체 A가 인터페이스의 메소드를 호출하면 인터페이스는 객체 B의 메소드를 호출하고 그 결과를 받아 객체 A에 전달해준다. 객체 A가 B를 바로 호출하지 않고 인터페이스를 거치는 이유는 객체 B와 객체 C의 교체를 용이하게 하기 위해서이다.
객체 A가 인터페이스의 메소드만 사용하고 인터페이스가 호출하는 객체만 바꿔준다면 객체 A의 소스코드를 변경하는 작업이 필요하지 않다. 하지만 실행 결과는 객체B, 객체C의 메소드로 다르기 때문에 인터페이스를 통해 다형성 구현이 가능하다.
인터페이스 선언
인터페이스의 접근 제한자는 default와 public을 사용한다
interface 인터페이스명 { } // default
public interface 인터페이스명 { } // public
구현 클래스 선언
인터페이스를 통해 객체를 호출하기 위해서는 인터페이스를 구현한(implement) 객체가 있어야 한다,
구현 객체는 다음과 같이 명시한다
public class B implements 인터페이스명 {}
구현 클래스는 인터페이스의 추상 메소드들 재정의하고 있어야 한다.
변수 선언과 구현 객체 대입
인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있다. 인터페이스는 참조 타입에 속하므로 인터페이스 변수에는 객체를 참조하고 있지 않다는 뜻으로 null을 대입할 수 있다.
인터페이스를 통해 구현 객체를 사용하려면, 인터페이스 변수에 구현 객체를 대입해야 한다. 구현 객체를 대입하면 변수를 통해 구현 객체에 재정의된 추상 메소드를 호출할 수 있다. 어떤 구현 객체가 대입되느냐에 따라 다른 메소드가 호출된다 (다형성)
다음은 Tevevision 객체와 Audio 객체를 각각 대입함에 따라 다른 메소드가 호출되는 것을 보여준다
interface RemoteControl {
public void turnOn();
}
class Television implements RemoteControl {
@override
public void turnOn(){
System.out.println("TV를 켭니다");
}
}
class Audio implements RemoteControl {
@Override
public void turnOn() {
System.out.println("Audio를 켭니다");
}
}
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn(); // TV를 켭니다
rc = new Audio();
rc.turnOn(); // Audio를 켭니다
}
}
상수 필드
인터페이스에 선언된 필드는 모두 public static final 특성을 갖는다.
따라서 public static final은 생략 가능하다. 상수명은 대문자로 작성한다.
상수는 구현 객체와 관련 없는 인터페이스 소속 멤버이므로 인터페이스로 바로 접근해서 상수값을 읽을 수 있다
interface RemoteControl {
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
}
public class RemoteControlExample {
public static void main(String[] args) {
System.out.println("리모콘 최대 볼륨 : " + RemoteControl.MAX_VOLUME); // 10
System.out.println("리모콘 최소 볼륨 : " + RemoteControl.MIN_VOLUME); // 0
}
}
추상 메소드
구현 클래스가 재정의해야 하는 추상 메소드를 말한다. 추상 메소드는 리턴 타입, 메소드명, 매개변수만 기술되고 중괄호를 붙이지 않는다. 인터페핑스 구현 객체는 추상 메소드의 실행부를 갖는 재정의한 메소드가 있어야 한다
디폴트 메소드
java8부터는 인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있다. 추상 메소드와 다르게 실행부인 중괄호가 있다. default 메소드는 상수 필드를 읽거나 추상 메소드를 호출하는 코드를 작성할 수 있다.
정적 메소드
정적 메소드도 선언이 가능하다. 추상 메소드와 디폴트 메소드는 구현 객체가 필요하지만, 정적 메소드는 구현 객체가 없어도 인터페이스만으로 호출할 수 있다.
interface RemoteControl {
// 상수 필드
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
// 추상 메소드
void turnOn();
void turnOff();
void setVolume(int volume);
// 디폴트 메소드
default void setMute(boolean mute) {
if (mute) {
System.out.println("무음 처리합니다");
setVolume(MIN_VOLUME);
} else {
System.out.println("무음 해제합니다");
}
}
// 정적 메소드
static void changeBattery() {
System.out.println("리모콘 건전지를 교환합니다");
}
}
class Audio implements RemoteControl {
private int volume;
// 추상 메소드 구현
@Override
public void turnOn() {
System.out.println("Audio를 켭니다");
}
@Override
public void turnOff() {
System.out.println("Audio를 끕니다");
}
@Override
public void setVolume(int volume) {
if (volume > RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if (volume < RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else this.volume = volume;
System.out.println("현재 Audio 볼륨 : " + volume);
}
private int memoryVolume;
// 디폴트메소드 재정의
@Override
public void setMute(boolean mute) {
if (mute) {
this.memoryVolume = this.volume;
System.out.println("무음처리합니다");
setVolume(RemoteControl.MIN_VOLUME);
} else {
System.out.println("무음 해제합니다");
setVolume(this.memoryVolume);
}
}
}
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
// 추상 메소드 호출
rc = new Audio();
rc.turnOn();
rc.setVolume(5);
// 디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
RemoteControl.changeBattery(); // 정적 메소드 호출
}
}
private 메소드
외부에서 접근할 수 없는 private 메소드 선언도 가능하다
private 메소드는 구현 객체가 필요하고, private 정적 메소드는 구현 객체가 필요하지 않다.
private 메소드의 용도는 디폴트와 정적 메소드들의 중복 코드를 줄이기 위함이다
interface Service {
default void defaultMethod1() {
System.out.println("Service.defaultMethod1");
defaultCommon();
}
default void defaultMethod2() {
System.out.println("Service.defaultMethod2");
}
private void defaultCommon() {
System.out.println("defaultMethod 중복 코드A");
System.out.println("defaultMethod 중복 코드B");
}
static void staticMethod1() {
System.out.println("Service.staticMethod1");
staticCommon();
}
static void staticMethod2() {
System.out.println("Service.staticMethod2");
staticCommon();
}
private static void staticCommon() {
System.out.println("staticMethod 중복 코드C");
System.out.println("staticMethod 중복 코드D");
}
}
public class ServiceImpl implements Service {
}
public class ServiceExample {
public static void main(String[] args) {
Service service = new ServiceImpl();
service.defaultMethod1();
System.out.println();
service.defaultMethod2();
System.out.println();
Service.staticMethod1();
System.out.println();
Service.staticMethod2();
System.out.println();
}
}
인터페이스 상속
인터페이스도 다른 인터페이스를 상속할 수 있으며, 클래스와 달리 다중 상속을 허용한다.
자식 인터페이스의 구현 클래스는 자식 인터페이스의 메소드뿐만 아니라 부모 인터페이스의 모든 추상 메소드를 재정의해야 한다. 그리고 구현 객체는 자식 및 부모 인터페이스 변수에 모두 대입이 가능하다
public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 {}
자식인터페이스 변수 = new 구현클래스();
부모인터페이스1 변수 = new 구현클래스();
부모인터페이스2 변수 = new 구현클래스();
타입 변환
인터페이스도 상속과 마찬가지로 타입 변환이 일어난다. 인터페이스 변수에 구현 객체를 대입하면 구현 객체는 인터페이스 타입으로 자동 타입 변환된다.
반대로 인터페이스 타입을 구현 클래스 타입으로 변환시킬 때는 강제 타입 변환이 필요하다. 구현 객체가 인터페이스 타입으로 자동 변환되면, 인터페이스에 선언된 메소드만 사용 가능하기 때문에 원래의 구현 클래스에만 존재하는 메소드를 사용하고 싶다면 구현 클래스 타입으로의 강제 타입 변환을 해주어야 한다.
타입 변환에 대한 자세한 내용은 상속에 정리해놓았다
'개발공부 > JAVA' 카테고리의 다른 글
[JAVA] 익명 객체 (0) | 2024.03.12 |
---|---|
[JAVA] 중첩 선언(중첩 클래스, 중첩 인터페이스) (0) | 2024.03.12 |
[JAVA] instanceof (0) | 2024.03.08 |
[JAVA] 상속 - 클래스와 메소드의 final 키워드 (0) | 2024.03.08 |
[JAVA] 상속 - super 키워드 (2) | 2024.03.08 |