본문 바로가기
공부기록/자바

Chapter 08. 인터페이스

by 루팽 2023. 9. 20.

인터페이스(interface)

사전적 의미로 두 장치를 연결하는 접속기

여기서 두 장치를 서로 다른 객체로 본다면, 두 객체를 연결하는 역할

객체 A는 인터페이스의 메소드만 사용하고, 인터페이스 뒤편의 객체 B 또는 객체 C의 메소드가 실행되기에 다형성 구현에 주된 기술로 이용됨

상속을 이용해 다형성을 구현할 수도 있지만, 인터페이스를 이용해서 다형성을 구현하는 경우가 더 많음

 

인터페이스 선언

class 키워드대신 interface 키워드 사용

interface 인터페이스명 { ... } // default 접근 제한
public interface 인터페이스명 { ... } // public 접근 제한

 

중괄호 안에는 인터페이스가 가지는 멤버 선언할 수 있음

public interface 인터페이스명 {
	// public 상수 필드
	// public 추상 메소드
	// public  디폴트 메소드
	// public  정적 메소드
	// private 메소드
	// private 정적 메소드
}

 

추상 메소드란 선언부만 있고 실행부인 중괄호가 없는 메소드

package ch08.sec02;

public interface RemoteControl {
	// public 추상 메소드
	public void turnOn();
}

 

구현 클래스 선언

객체 A가 인터페이스의 추상 메소드를 호출하면 인터페이스는 객체 B의 메소드 실행하는데, 이 때 객체 B는 인터페이스에 선언된 추상 메소드와 동일한 선언부를 가진 (재정의된) 메소드를 가지고 있어야 함

여기서 객체 B를 인터페이스를 구현한(implement) 객체라고 함

인터페이스에 정의된 추상 메소드에 대한 실행 내용이 구현(직성)되어 있기 때문

객체 B와 같은 구현 객체는 아래와 같이 인터페이스를 구현하고 있음을 선언부에 명시해야 함

public class B implements 인터페이스명 { ... }

 

Television 구현 클래스를 생성하고 RemoteControl의 추상 메소드인 turnOn()을 아래와 같이 재정의

package ch08.sec02;

public class Television implements RemoteControl {
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다."); // 인터페이스에 선언된 turnOn() 추상메소드 재정의
	}
}

 

변수 선언과 구현 객체 대입

인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있음

참조타입에 속하므로 null 대입 가능

RemoteControl rc;
RemoteControl rc = null;

 

인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현 객체(의 번지)를 대입해야 함

rc = new Television();

 

만약 Television이 implements RemoteControl로 선언되지 않았다면 RemoteControl 타입의 변수 rc에 대입할 수 없음

인터페이스 변수에 구현 객체가 대입되었다면 변수를 통해 인터페이스의 추상 메소드를 호출할 수 있음

rc.turnOn();

 

인터페이스 변수를 통해 turnOn()메소드가 호출되면 Television에서 재정의된 turnOn() 메소드가 실행됨

package ch08.sec02;

public class RemoteControlExample {
	public static void main(String[] args) {
		RemoteControl rc;
		rc = new Television();
		rc.turnOn(); // TV를 켭니다.
	}
}

 

아래와 같이 변수 선언과 동시에 구현 객체를 대입할 수도 있음

RemoteControl rc = new Television();

 

rc변수에는 RemoteControl을 구현한 어떠한 객체든 대입 가능

만약 Audio객체가 구현 객체라면 아래와 같이 대입 가능

이 경우 실제 실행되는 것은 Audio에서 재정의된 turnOn() 메소드임

rc = new Audio();
rc.turnOn();

 

package ch08.sec02;

public class Audio implements RemoteControl {
	@Override
	public void turnOn() {
		System.out.println("Audio를 켭니다.");
	}
}

 

package ch08.sec02;

public class RemoteControlExample {
	public static void main(String[] args) {
		RemoteControl rc;
		
		// rc 변수에 Television 객체를 대입
		rc = new Television();
		rc.turnOn(); // TV를 켭니다.
		
		// rc 변수에 Audio객체를 대입(교체시킴)
		rc = new Audio();
		rc.turnOn(); // Audio를 켭니다.
	}
}

 

상수 필드

인터페이스는 public static final 특성을 갖는 불변의 상수 필드를 멤버로 가질 수 있음

[public static final] 타입 상수명 = 값;

 

인터페이스에 선언된 필드는 모두 public static final 특성을 갖기에 생략하더라도 컴파일 과정에서 자동적으로 붙게 됨

상수명은 대문자로 작성하되 서로 다른 단어로 구성되어 있은 경우 언더바(_)로 연결함

package ch08.sec03;

public interface RemoteControl {
	// 상수 선언
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
}

 

상수는 구현 객체와 관련 없는 인터페이스 소속 멤버이므로 아래와 같이 인터페이스로 바로 접근해서 상수값을 읽을 수 있음

package ch08.sec03;

public class RemoteControlExample {
	public static void main(String[] args) {
		System.out.println("리모콘 최대 볼륨: " + RemoteControl.MAX_VOLUME); // 리모콘 최대 볼륨: 10
		System.out.println("리모콘 최저 볼륨: " + RemoteControl.MIN_VOLUME); // 리모콘 최저 볼륨: 0
	}
}

 

추상 메소드

인터페이스는 구현 클래스가 재정의해야 하는 public 추상 메소드를 멤버로 가질 수 있음

추상 메소드는 리턴타입, 메소드명, 매개변수만 기술되고 중괄호 {}를 붙이지 않는 메소드

public abstract를 생략하더라도 컴파일 과정에서 자동으로 붙게 됨

[ public abstract ] 리턴타입 메소드명(매개변수);

 

추상 메소드는 객체 A가 인터페이스를 통해 어떻게 메소드를 호출할 수 있는지 방법을 알려주는 역할

인터페이스 구현 객체 B는 추상 메소드의 실행부를 갖는 재정의된 메소드가 있어야 함

package ch08.sec04;

public interface RemoteControl {
	// 상수 필드
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
	
	// 추상 메소드 - 메소드 선언부만 작성
	void turnOn();
	void turnOff();
	void setVolume(int volume);
}

 

구현 클래스인 Television과 Audio는 인터페이스에 선언된 모든 추상 메소드를 재정의해서 실행 코드를 가져야 함

package ch08.sec04;

public class Television implements RemoteControl {
	// 필드
	private int volume;
	
	// turnOn() 추상 메소드 오버라이딩
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	// turnOff() 추상 메소드 오버라이딩
	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
	}

	// setVolume() 추상 메소드 오버라이딩
	@Override
	public void setVolume(int volume) {
		// 인터페이스 상수 필드를 이용해서 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("현재 TV 볼륨: " + volume);
	}
}

 

package ch08.sec04;

public class Audio implements RemoteControl{
	// 필드
	private int volume;
	
	// turnOn() 추상 메소드 오버라이딩
	@Override
	public void turnOn() {
		System.out.println("Audio를 켭니다.");
	}

	// turnOff() 추상 메소드 오버라이딩
	@Override
	public void turnOff() {
		System.out.println("Audio를 끕니다.");
	}

	// setVolume() 추상 메소드 오버라이딩
	@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);
	}
}

 

인터페이스의 추상 메소드는 기본적으로 public 접근 제한을 갖기에 public보다 더 낮은 접근 제한으로 재정의할 수 없다는 것을 주의해야 함

그렇기에 재정의되는 메소드에는 모두 public이 추가되어 있음

인터페이스로 구현 객체를 사용하려면 아래와 같이 인터페이스 변수를 선언하고 구현 객체를 대입해야 함

인터페이스 변수는 참조 타입이기에 구현 객체가 대입되면 구현 객체의 번지를 저장함

RemoteControl rc;
rc = new Television();

// 선언과 동시에 대입
RemoteControl rc = new Audio();

 

구현 객체가 대입되면 인터페이스 변수로 추상 메소드를 호출할 수 있는데, 어떤 구현 객체가 대입되었는지에 따라 실행 내용이 달라짐

package ch08.sec04;

public class RemoteControlExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언
		RemoteControl rc;
		
		// Television 객체를 생성하고 인터페이스 변수에 대입
		rc = new Television();
		rc.turnOn(); // TV를 켭니다.
		rc.setVolume(5); // 현재 TV 볼륨: 5
		rc.turnOff(); // TV를 끕니다.
		
		// Audio 객체를 생성하고 인터페이스 변수에 대입
		rc = new Audio();
		rc.turnOn(); // Audio를 켭니다.
		rc.setVolume(5); // 현재 Audio 볼륨: 5
		rc.turnOff(); // Audio를 끕니다.
	}
}

 

디폴트 메소드

인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있고 추상 메소드는 실행부(중괄호 {})가 없지만, 디폴트 메소드는 실행부가 있음

클래스 메소드와 동일하게 선언하지만 default 키워드가 리턴 타입 앞에 붙음

[public] default 리턴타입 메소드명(매개변수) { ... }

 

디폴트 메소드의 실행부에는 상수 필드를 읽거나 추상 메소드를 호출하는 코드 작성할 수 있음

package ch08.sec05;

public 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("무음 해제합니다.");
		}
	}
}

 

디폴트 메소드는 구현 객체가 필요한 메소드

따라서 RemoteControl의 setMute() 메소드를 호출하려면 구현 객체인 Television 객체를 인터페이스 변수에 대입하고 나서 setMute()를 호출해야 함

package ch08.sec05;

public class RemoteControlExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언
		RemoteControl rc;
		
		//Television 객체를 생성하고 인터페이스 변수에 대입
		rc = new Television();
		rc.turnOn(); // TV를 켭니다.
		rc.setVolume(5); // 현재 TV 볼륨: 5
		
		// 디폴트 메소드 호출
		rc.setMute(true); // 무음 처리합니다. 현재 TV 볼륨: 0
		rc.setMute(false); // 무음 해제합니다.
	}
}

 

구현 클래스는 디폴트 메소드를 재정의해서 수정할 수 있음

재정의 시 주의할 점은 public 접근 제한자를 반드시 붙여야 하고, default 키워드를 생략하는 것

package ch08.sec05;

public class Audio implements RemoteControl {
	// 필드
	private int volume;

	// turnOn() 추상 메소드 오버라이딩
	@Override
	public void turnOn() {
		System.out.println("Audio를 켭니다.");
	}

	// turnOff() 추상 메소드 오버라이딩
	@Override
	public void turnOff() {
		System.out.println("Audio를 끕니다.");
	}

	// setVolume() 추상 메소드 오버라이딩
	@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(MIN_VOLUME);
		} else {
			System.out.println("무음 해제합니다.");
			setVolume(this.memoryVolume); // mute가 false일 결우 원래 볼륨으로 복원
		}
	}
}

 

package ch08.sec05;

public class RemoteControlExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언
		RemoteControl rc;
		
		//Television 객체를 생성하고 인터페이스 변수에 대입
		rc = new Television();
		rc.turnOn(); // TV를 켭니다.
		rc.setVolume(5); // 현재 TV 볼륨: 5
		
		// 디폴트 메소드 호출
		rc.setMute(true); // 무음 처리합니다. 현재 TV 볼륨: 0
		rc.setMute(false); // 무음 해제합니다.
		
		System.out.println();
		
		// Audio 객체를 생성하고 인터페이스 변수에 대입
		rc = new Audio();
		rc.turnOn(); // Audio를 켭니다.
		rc.setVolume(5); // 현재 Audio 볼륨: 5
		
		// 디폴트 메소드 호출
		rc.setMute(true); // 무음 처리합니다. 현재 Audio 볼륨: 0
		rc.setMute(false); // 무음 해제합니다. 현재 Audio 볼륨: 5
	}
}

 

정적 메소드

인터페이스에는 정적 메소드도 선언 가능

추상 메소드와 디폴트 메소드는 구현 객체가 필요

정적 메소드는 구현 객체가 없어도 인터페이스만으로 호출 가능

선언 방법은 클래스 정적 메소드와 동일하나 public을 생략하더라도 컴파일과정에서 자동으로 붙음

[public | private] static 리턴타입 메소드명(매개변수, ...) { ... }

 

package ch08.sec06;

public interface RemoteControl {
	// 상수 필드
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 10;
	
	// 추상 메소드
	void turnOn();
	void turnOff();
	void setVolume(int volume);
	
	// 디폴트 메소드
	default void setMute(boolean mute) {
		// 이전 예제와 동일
	}
	
	// 정적 메소드
	static void changeBattery() {
		System.out.println("리모콘 건전지를 교환합니다.");
	}
}

 

인터페이스에 선언된 정적 메소드는 구현 객체 없이 인터페이스 명으로 접근해서 호출 가능

따라서 RemoteControl.changeBattery()로 호출할 수 있음

정적메소드의 실행부(중괄호 {})를 작성할 때 주의점은, 상수 필드를 제외한 추상 메소드, 디폴트 메소드, private메소드 등을 호출할 수 없다는 것 → 이 메소드는 구현 객체가 필요한 인스턴스 메소드이기 때문임

package ch08.sec06;

import ch08.sec05.Audio;
import ch08.sec05.Television;

public class RemoteControlExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언
		RemoteControl rc;
		
		// Television 객체를 생성하고 인터페이스 변수에 대입
		rc = new Television();
		rc.turnOn();
		rc.setVolume(5);
		
		// 디폴트 메소드 호출
		rc.setMute(true);
		rc.setMute(false);
		
		System.out.println();
		
		// Audio 객체를 생성하고 인터페이스 변수에 대입
		rc = new Audio();
		rc.turnOn();
		rc.setVolume(5);
		
		// 디폴트 메소드 호출
		rc.setMute(true);
		rc.setMute(false);
		
		System.out.println();
		
		// 정적 메소드 호출
		RemoteControl.changeBattery(); // 리모콘 건전지를 교환합니다.
	}
}

 

private 메소드

인터페이스의 상수 필드, 추상, 디폴트, 정적 메소드는 모두 public 접근 제한

이 멤버들을 선언할 때 public을 생략하더라도 컴파일 과정에서 public 접근제한자가 붙어 항상 외부에서 접근 가능

인터페이스 외부에서 접근할 수 없는 private 메소드 선언도 가능함

구분  설명
private 메소드 구현 객체가 필요한 메소드
private 정적 메소드 구현 객체가 필요없는 메소드

 

private 메소드는 디폴트 메소드 안에서만 호출 가능

private 정적 메소드는 디폴트 메소드뿐만 아니라 정적 메소드 안에서도 호출 가능 → 디폴트와 정적 메소드들의 중복 코드를 줄이기 위함

 

package ch08.sec07;

public interface Service {
	// 디폴트 메소드
	default void defaultMethod1() {
		System.out.println("defaultMethod1 종속 코드");
		defaultCommon();
	}
	
	default void defaultMethod2() {
		System.out.println("defaultMethod2 종속 코드");
		defaultCommon();
	}
	
	// private 메소드
	private void defaultCommon() {
		System.out.println("defaultMethod 중복 코드 A");
		System.out.println("defaultMethod 중복 코드 B");
	}
	
	// 정적 메소드
	static void staticMethod1() {
		System.out.println("staticMethod1 종속 코드");
		staticCommon();
	}
	
	static void staticMethod2() {
		System.out.println("staticMethod2 종속 코드");
		staticCommon();
	}
	
	// private 정적 메소드
	private static void staticCommon() {
		System.out.println("staticMethod 중복 코드 C");
		System.out.println("staticMethod 중복 코드 D");
	}
}

 

package ch08.sec07;

public class ServiceImpl implements Service{
}

 

package ch08.sec07;

public class ServiceExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언과 구현 객체 대입
		Service service = new ServiceImpl();
		
		// 디폴트 메소드 호출
		service.defaultMethod1();
		System.out.println();
		service.defaultMethod2();
		System.out.println();	
		
		System.out.println("=====");
		System.out.println();	
		
		// 정적 메소드 호출
		Service.staticMethod1();
		System.out.println();
		Service.staticMethod2();
		System.out.println();
	}
}
/*
defaultMethod1 종속 코드
defaultMethod 중복 코드 A
defaultMethod 중복 코드 B

defaultMethod2 종속 코드
defaultMethod 중복 코드 A
defaultMethod 중복 코드 B

=====

staticMethod1 종속 코드
staticMethod 중복 코드 C
staticMethod 중복 코드 D

staticMethod2 종속 코드
staticMethod 중복 코드 C
staticMethod 중복 코드 D
*/

 

다중 인터페이스 구현

구현 객체는 여러 개의 인터페이스를 implements 할 수 있음

구현 객체가 인터페이스 A와 인터페이스 B를 구현하고 있다면, 각각의 인터페이스를 통해 구현 객체를 사용할 수 있음

 

구현 클래스는 아래와 같이 인터페이스 A와 인터페이스 B를 implements 뒤에 쉼표로 구분해서 작성해, 모든 인터페이스가 가진 추상 메소드를 재정의해야 함

public class 구현클래스명 implements 인터페이스A, 인터페이스B {
	// 모든 추상 메소드 재정의
}

 

인터페이스 A와 인터페이스 B를 구현한 객체는 아래와 같이 두 인터페이스 타입의 변수에 각각 대입될 수 있음

인터페이스A 변수 = new 구현클래스명(...);
인터페이스B 변수 = new 구현클래스명(...);

 

구현 객체가 어떤 인터페이스 변수에 대입되느냐에 따라 변수를 통해 호출할 수 있는 추상 메소드가 결정됨

package ch08.sec08;

public interface RemoteControl {
	// 추상 메소드
	void turnOn();
	void turnOff();
}

 

package ch08.sec08;

public interface Searchable {
	// 추상 메소드
	void search(String url);
}

 

package ch08.sec08;

public class SmartTelevision implements RemoteControl, Searchable{
	// turnOn() 추상메소드 오버라이딩	
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다.");
	}

	// turnOff() 추상메소드 오버라이딩	
	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다.");
	}
	
	// search() 추상메소드 오버라이딩
	@Override
	public void search(String url) {
		System.out.println(url + "을 검색합니다.");
	}
}

 

package ch08.sec08;

public class MultiInterfaceImpleExample {
	public static void main(String[] args) {
		// RemoteControl 인터페이스 변수 선언 및 구현 객체 대입
		RemoteControl rc = new SmartTelevision();
		// RemoteControl 인터페이스에 선언된 추상 메소드만 호출 가능
		rc.turnOn();
		rc.turnOff();
		
		// Searchable 인터페이스 변수 선언 및 구현 객체 대입
		Searchable searchable = new SmartTelevision();
		// Searchable 인터페이스에 선언된 추상 메소드만 호출 가능
		searchable.search("<https://www.youtube.com>");
	}
}

/*
TV를 켭니다.
TV를 끕니다.
<https://www.youtube.com을> 검색합니다.
 */

 

인터페이스 상속

인터페이스도 다른 인터페이스 상속 가능 → 클래스와 달리 다중 상속 허용함

public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 { ... }

 

자식 인터페이스의 구현클래스는 자식 인터페이스의 메소드뿐만 아니라 부모 인터페이스의 모든 추상메소드를 재정의해야 함

또한 구현 객체는 아래와 같이 자식 및 부모 인터페이스 변수에 대입될 수 있음

자식인터페이스 변수 = new 구현클래스();
부모인터페이스1 변수 = new 구현클래스();
부모인터페이스2 변수 = new 구현클래스();

 

구현 객체가 자식 인터페이스 변수에 대입되면 자식 및 부모 인터페이스의 추상 메소드를 모두 호출 가능

하지만 부모 인터페이스 변수에 대입되면 부모 인터페이스에 선언된 추상 메소드만 호출 가능

package ch08.sec09;

public interface InterfaceA {
	// 추상 메소드
	void methodA();
}

 

package ch08.sec09;

public interface InterfaceB {
	// 추상 메소드
	void methodB();
}

 

package ch08.sec09;

public interface InterfaceC extends InterfaceA, InterfaceB{
	// 추상 메소드
	void methodC();
}

 

package ch08.sec09;

public class InterfaceCImpl implements InterfaceC{
	@Override
	public void methodA() {
		System.out.println("InterfaceCImpl-methodA() 실행");		
	}

	@Override
	public void methodB() {
		System.out.println("InterfaceCImpl-methodB() 실행");
	}

	@Override
	public void methodC() {
		System.out.println("InterfaceCImpl-methodc() 실행");		
	}
}

 

package ch08.sec09;

public class ExtendsExample {
	public static void main(String[] args) {
		InterfaceCImpl impl = new InterfaceCImpl();
		
		InterfaceA ia = impl;
		ia.methodA();
//		ia.methodB(); 사용 불가!
		System.out.println();
		
		InterfaceB ib = impl;
//		ib.methodA(); 사용 불가!
		ib.methodB();
		System.out.println();
		
		InterfaceC ic = impl;
		ic.methodA();
		ic.methodB();
		ic.methodC();
	}
}
/*
InterfaceCImpl-methodA() 실행

InterfaceCImpl-methodB() 실행

InterfaceCImpl-methodA() 실행
InterfaceCImpl-methodB() 실행
InterfaceCImpl-methodc() 실행
 */

 

타입 변환

인터페이스의 타입 변환은 인터페이스와 구현 클래스 간에 발생함

인터페이스 변수에 구현 객체를 대입하면 구현 객체는 인터페이스 타입으로 자동 타입 변환됨

반대로 인터페이스 타입을 구현 객체 클래스 타입으로 변환시킬 땐 강제 타입 변환이 필요함

인터페이스 변수 = 구현객체; // 자동 타입 변환됨

 

부모 클래스가 인터페이스를 구현하고 있다면 자식 클래스도 인터페이스 타입으로 자동 타입 변환될 수 있음

인터페이스 A를 구현한 B, C 클래스가 있고, B를 상속한 D클래스, C를 상속한 E클래스가 있을 때 B, C, D, E로부터 생성된 객체는 A를 직, 간접적으로 구현하고 있기 때문에 모두 인터페이스 A로 자동 타입 변환될 수 있음

package ch08.sec10.exam01;

public interface A {
}

 

package ch08.sec10.exam01;

public class B implements A {
}

 

package ch08.sec10.exam01;

public class C implements A {
}

 

package ch08.sec10.exam01;

public class D extends B {
}

 

package ch08.sec10.exam01;

public class E extends C {
}

 

package ch08.sec10.exam01;

public class PromotionExample {
	public static void main(String[] args) {
		// 구현 객체 생성
		B b = new B();
		C c = new C();
		D d = new D();
		E e = new E();
		
		// 인터페이스 변수 선언
		A a;
		
		// 변수에 구현 객체 대입
		a = b; // A <- B 자동 타입 변환
		a = c; // A <- C 자동 타입 변환
		a = d; // A <- D 자동 타입 변환
		a = e; // A <- E 자동 타입 변환
	}
}

 

강제 타입 변환

캐스팅(casting) 기호를 사용해서 인터페이스 타입을 구현 클래스 타입으로 변환시키는 것

구현클래스 변수 = (구현클래스) 인터페이스변수;

 

구현 객체가 인터페이스 타입으로 자동 변환되면 인터페이스에 선언된 메소드만 사용 가능

RemoteControl 인터페이스에 3개의 메소드, Television 클래스에 5개의 메소드가 선언되어 있다면 RemoteControl 인터페이스로 호출 가능한 메소드는 3개뿐

RemoteControl rc = new Television();
rc.turnOn();
rc.turnOff();
rc.setVolume(5);

 

자동 타입 변환 후에 Television의 나머지 2개 메소드를 호출하고 싶다면 아래와 같이 캐스팅 기호를 사용해서 강제 타입 변환을 해야 함

Television tv = (Television) rc;
tv.turnOn();
tv.turnOff();
tv.setVolume(5);
tv.setTime();
tv.record();

 

package ch08.sec10.exam02;

public interface Vehicle {
	// 추상 메소드
	void run();
}

 

package ch08.sec10.exam02;

public class Bus implements Vehicle{
	// 추상 메소드 재정의
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
	// 추가 메소드
	public void checkFare() {
		System.out.println("승차요금을 체크합니다.");
	}
}

 

package ch08.sec10.exam02;

public class CastingExample {
	public static void main(String[] args) {
		// 인터페이스 변수 선언과 구현 객체 대입
		Vehicle vehicle = new Bus();
		
		// 인터페이스 통해서 호출
		vehicle.run();
//		vehicle.checkFare(); 사용 불가!
		
		// 강제 타입 변환 후 호출
		Bus bus = (Bus) vehicle;
		bus.run();
		bus.checkFare();
	}
}

/*
버스가 달립니다.
버스가 달립니다.
승차요금을 체크합니다.
*/

 

다형성

다형성이란 사용 방법은 동일하지만 다양한 결과가 나오는 성질을 말함

인터페이스는 다형성을 구현하는 주된 기술로 사용됨

구현객체 B와 C 중 어느 객체가 인터페이스에 대입되었느냐에 따라 객체 A의 메소드 호출 결과는 달라질 수 있음

상속의 다형성과 마찬가지로 인터페이스 역시 다형성을 구현하기 위해 재정의와 자동 타입 변환 기능을 이용함

메소드 재정의 + 자동 타입 변환 => 다형성

인터페이스의 추상 메소드는 구현 클래스에서 재정의하고, 재정의되는 내용은 구현 클래스마다 다름

구현 객체는 인터페이스 타입으로 자동 타입 변환되고, 인터페이스 메소드 호출 시 구현 객체의 재정의된 메소드가 호출되어 다양한 실행 결과를 얻을 수 있음

 

필드의 다형성

자동차는 동일한 타이어 인터페이스로 한국타이어와 금호타이어를 사용하지만 각 타이어의 성능을 다름(다형성)

자동차를 설계할 때 아래와 같이 필드 타입으로 타이어 인터페이스를 선언하면 필드값으로 한국 또는 금호 타이어 객체를 대입할 수 있음 → 자동 타입 변환

public class Car {
	Tire tire1 = new HankookTire();
	Tire tire2 = new KumhoTire();
}

 

Car 객체를 생성한 후 다른 구현 객체를 대입할 수도 있음 → 타이어 교체에 해당됨

tire1과 tire2 필드에 어떠한 타이어 구현 객체가 대입되어도 Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 전혀 문제되지 않음

Car myCar = new Car();
myCar.tire1 = new KumhoTire();

tire1과 tire2 필드에 어떠한 타이어 구현 객체가 대입되어도 Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 전혀 문제되지 않음

 

package ch08.sec11.exam01;

public interface Tire {
	// 추상 메소드
	void roll();
}

 

package ch08.sec11.exam01;

public class HankookTire implements Tire{
	// 추상 메소드 재정의
	@Override
	public void roll() {
		System.out.println("한국 타이어가 굴러갑니다.");
	}
}

 

package ch08.sec11.exam01;

public class KumhoTire implements Tire{
	// 추상 메소드 재정의
	@Override
	public void roll() {
		System.out.println("금호 타이어가 굴러갑니다.");
	}
}

 

package ch08.sec11.exam01;

public class Car {
	// 필드
	Tire tire1 = new HankookTire();
	Tire tire2 = new HankookTire();
	
	// 메소드
	void run() {
		// 인터페이스에 선언된 추상 메소드 호출 -> 구현 객체의 roll() 메소드 실행
		tire1.roll();
		tire2.roll();
	}
}

 

package ch08.sec11.exam01;

public class CarExample {
	public static void main(String[] args) {
		// 자동차 객체 생성
		Car myCar = new Car();
		
		// run() 메소드 실행
		myCar.run();
		
		// 타이어 객체 교체
		myCar.tire1 = new KumhoTire();
		myCar.tire2 = new KumhoTire();
		
		// run() 메소드 실행(다형성-실행 결과가 다름)
		myCar.run();
	}
}

/*
한국 타이어가 굴러갑니다.
한국 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
금호 타이어가 굴러갑니다.
*/

 

매개변수의 다형성

메소드 호출 시 매개값 다양화 → 매개변수 타입을 부모타입으로 선언, 호출할 때에는 자식객체 대입

비슷한 원리로 매개변수 타입을 인터페이스로 선언하면 메소드 호출 시 다양한 구현 객체를 대입할 수 있음

 

아래와 같이 Vehicle 인터페이스가 선언되었을 때, 운전자 클래스인 Driver는 다양한 Vehicle 구현 객체를 운전하기 위해 Vehicle 인터페이스를 매개변수로 가지는 drive() 메소드를 선언함

public interface Vehicle {
	void run();
}

 

public class Driver {
	void drive(Vehicle vehicle) {
		vehicle.run();
	}
}

 

Bus가 Vehicle의 구현 클래스라면 아래와 같이 Driver의 drive() 메소드를 호출할 때 Bus 객체를 생성해서 매개값으로 줄 수 있음

Driver driver = new Driver();
Bus bus = new Bus();
driver.drive(bus); // 자동 타입 변환 발생 -> Vehicle vehicle = bus;

 

drive() 메소드를 호출할 때 인터페이스 Vehicle을 구현하는 어떠한 객체라도 매개값으로 줄 수 있는데, 어떤 객체를 주느냐에 따라 run() 메소드의 실행 결과가 다르게 나옴 → 구현 객체에서 재정의된 ruin() 메소드의 실행 내용이 다르기 때문 → 매개변수의 다형성

void drive(Vehicle vehicle) {
	vehicle.run(); // 구현 객체가 재정의한 run() 메소드가 실행됨
}

 

package ch08.sec11.exam02;

public interface Vehicle {
	// 추상 메소드
	void run();
}

 

package ch08.sec11.exam02;

public class Driver {
	// 구현 객체가 대입될 수 있도록 매개변수를 인터페이스 타입으로 선언
	void drive(Vehicle vehicle) {
		vehicle.run(); // 인터페이스 메소드 호출
	}
}

 

package ch08.sec11.exam02;

public class Bus implements Vehicle{
	// 추상 메소드 재정의
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}

 

package ch08.sec11.exam02;

public class Taxi implements Vehicle{
	// 추상 메소드 재정의
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}	
}

 

package ch08.sec11.exam02;

public class DriverExample {
	public static void main(String[] args) {
		// Driver 객체 생성
		Driver driver = new Driver();
		
		// Vehicle 구현 객체 생성
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		// 매개값으로 구현 객체 대입(다형성-실행 결과가 다름)
		driver.drive(bus); // 자동 타입 변환 -> 오버라이딩 메소드 호출 -> 다형성
		driver.drive(taxi); // 자동 타입 변환 -> 오버라이딩 메소드 호출 -> 다형성
	}
}

/*
버스가 달립니다.
택시가 달립니다.
*/

 

객체 타입 확인

상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용했는데, 인터페이스에서도 사용할 수 있음

Vehicle 인터페이스 변수에 대입된 객체가 Bus인지 확인하는 코드는 아래와 같음

if(vehicle instanceof Bus) {
	// vehicle에 대입된 객체가 Bus일 경우 실행
}

 

메소드의 매개변수가 인터페이스 타입을 경우, 메소드 호출 시 매개값은 해당 인터페이스를 구현하는 모든 객체가 될 수 있음

만약 매개값이 특정 구현 객체일 경우에만 강제 타입 변환을 하고 싶다면 instanceof 연산자를 사용해서 매개값의 타입을 검사해야 함

public void method(Vehicle vehicle) {
	if(vehicle instanceof Bus) {
		Bus bus = (Bus) vehicle;
		// bus 변수 사용
	}
}

 

Java 12부턴 instanceof 연산의 결과가 true일 경우, 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환이 필요 없음

if(vehicle instanceof Bus bus) {
	// bus 변수 사용
}

 

package ch08.sec12;

public interface Vehicle {
	void run();
}

 

package ch08.sec12;

public class Taxi implements Vehicle{
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}	
}

 

package ch08.sec12;

public class Bus implements Vehicle{
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
	public void checkFare() {
		System.out.println("승차요금을 체크합니다.");
	}
}

 

package ch08.sec12;

public class InstanceofExample {
	public static void main(String[] args) {
		// 구현 객체 생성
		Taxi taxi = new Taxi();
		Bus bus = new Bus();
		
		// ride() 메소드 호출 시 구현 객체를 매개값으로 전달
		ride(taxi);
		System.out.println();
		ride(bus);
	}
	
	// 인터페이스 매개변수를 갖는 메소드
	public static void ride(Vehicle vehicle) {
		// 방법 1
		// 매개값이 Bus인 경우에만 강제타입변환하여 checkFare() 메소드 호출
		/*if(vehicle instanceof Bus) {
			Bus bus = (Bus) vehicle;
			bus.checkFare();
		}*/
		
		// 방법 2
		// Java 12부터 사용 가능
		if(vehicle instanceof Bus bus) {
			bus.checkFare();
		}
		
		vehicle.run();
	}
}

/*
택시가 달립니다.

승차요금을 체크합니다.
버스가 달립니다.
*/

 

봉인된 인터페이스

Java 15부터는 무분별한 자식 인터페이스 생성을 방지하기 위해 봉인된(sealed) 인터페이스를 사용할 수 있음

InterfaceA의 자식 인터페이스는 InterfaceB만 가능하고, 그 이외는 자식 인터페이스가 될 수 없도록 아래와 같이 InterfaceA를 봉인된 인터페이스로 선언할 수 있음

public sealed interface InterfaceA permits InterfaceB { ... }

 

sealed 키워드를 사용하면 permits 키워드 뒤에 상속 가능한 자식 인터페이스를 지정해야 함

봉인된 InterfaceA를 상속하는 InterfaceB는 non-sealed 키워드로 아래와 같이 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 인터페이스로 선언해야 함

public non-sealed interface InterfaceB extends InterfaceA { ... }

 

non-sealed는 봉인을 해제한다는 뜻으로, InterfaceB는 다른 자식 인터페이스를 만들 수 있음

public interface InterfaceC extends InterfaceB { ... }

 

package ch08.sec13;

public sealed interface InterfaceA permits InterfaceB{
	void methodA();
}

 

package ch08.sec13;

public non-sealed interface InterfaceB extends InterfaceA{
	void methodB();
}

 

package ch08.sec13;

public interface InterfaceC extends InterfaceB{
	void methodC();
}

 

package ch08.sec13;

public class ImplClass implements InterfaceC{
	@Override
	public void methodA() {
		System.out.println("methodA() 실행");
	}
	
	@Override
	public void methodB() {
		System.out.println("methodB() 실행");		
	}

	@Override
	public void methodC() {
		System.out.println("methodC() 실행");
	}
}

 

package ch08.sec13;

public class SealedExample {
	public static void main(String[] args) {
		ImplClass impl = new ImplClass();
		
		InterfaceA ia = impl;
		ia.methodA();
		System.out.println();
		
		InterfaceB ib = impl;
		ib.methodA();
		ib.methodB();
		System.out.println();
		
		InterfaceC ic = impl;
		ic.methodA();
		ic.methodB();
		ic.methodC();
	}
}
/*
methodA() 실행

methodA() 실행
methodB() 실행

methodA() 실행
methodB() 실행
methodC() 실행
*/

'공부기록 > 자바' 카테고리의 다른 글

Chapter 07. 상속  (1) 2023.02.06
Chapter 06. 클래스  (0) 2022.12.18
Chapter 05. 참조 타입  (0) 2022.11.23
Chapter 04. 조건문과 반복문  (0) 2022.11.22
Chapter 03. 연산자  (0) 2022.11.21

댓글