인터페이스

이동주·2025년 3월 10일

JAVA

목록 보기
18/30

인터페이스

두 객체를 연결하는 역할을 함

  • 다형성 구현에 주된 기술로 사용됨
  • 실체가 존재하는 구현체가 아니고, 객체 간의 연결을 위한 약속 같은 개념
  • 사용자와 구현자의 중간 교량 역할을 함
  • 추상 클래스와 기능이 비슷한데, 메소드를 선언만 하고 실행문 블록은 만들지 않음
  • 코드를 일반화 시킬 수 있음
  • 다중 상속이 가능함

구조

인터페이스와 구현클래스 선언

인터페이스 선언

  • 인터페이스 선언은 class 대신 interface 키워드를 사용함
  • 멤버 변수를 사용하지 않고 함수에 대해 정의만 함
  • 접근 제한자로는 클래스와 마찬가지로 default와 public을 붙일 수 있음
  • 인터페이스에서 만드는 메소드는 모두 추상 메소드
  • 추상 메소드와 다르게 abstract를 생략 가능
  • 구현자를 만들 경우 static(정적) 키워드를 선언해야 함
  • 접근 권한을 적지 않으면 무조건 public!
// 인터페이스 선언
// public 또는 default 형태로 접근이 가능함
public interface 인터페이스명{
	public 메소드{ // abstract 생략 가능
        // 선언 가능한 메소드/필드 종류
        // public 상수 필드
        // public 추상 메소드
        // public 디폴트 메소드
        // public 정적 메소드
        // private 메소드
        // private 정적 메소드
    }
}
package ch08.sec02;

// 인터페이스 선언
public interface RemoteControl {
	// 추상 메소드 선언 (abstract 생략 가능)
	public void turnOn();
}

구현 클래스 선언

  • 인터페이스에 정의된 추상 메소드에 대한 실행 내용을 구현
  • implements라는 키워드를 이용해 해당 인터페이스 클래스를 사용할 수 있음
    -> (extends 역할이라 보면 됨)
  • 재정의 메소드이고 접근 변경자는 public으로 해야 함
// 구현 클래스 선언
public class 클래스명 implements 인터페이스명
	public void 메소드명() {
    	// 인터페이스에서 선언한 메소드를 해당 클래스에서 구현
        // 해당 클래스에서 메소드를 선언하지 않으면 오류가 남
    }
}
package ch08.sec02;

// implements : 인터페이스를 가져오는 역할 (extends와 기능 비슷함)
public class Television implements RemoteControl{
	
	// 인터페이스에서 선언한 메소드를 꼭 실행문을 넣어 실행시켜야함
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다");
	}
}
package ch08.sec02;

//implements : 인터페이스를 가져오는 역할 (extends와 기능 비슷함)
public class Audio implements RemoteControl{
	
	// 인터페이스에서 선언한 메소드를 꼭 실행문을 넣어 실행시켜야함
	@Override
	public void turnOn() {
		System.out.println("Audio를 켭니다");
	}
}

변수 선언과 구현 객체 대입

  • 인터페이스는 참조 변수로만 사용 가능함
  • 추상 클래스와 같이 추상적인 설계도 역할을 하기 때문에 new 연산자로 객체 생성이 불가함
  • 인터페이스는 참조 타입에 속하기 때문에 인터페이스 변수에는 객체를 참조하고 있지 않음
  • 따라서 null을 대입할 수 있음
  • 인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현 객체의 번지를 대입해야 함
package ch08.sec02;

public class RemoteControlExam {
	public static void main(String[] args) {
		RemoteControl rc;
		// 인터페이스 참조변수를 선언
        // 객체를 생성할 수 없기 때문에 인터페이스를 구현한 
        // 클래스의 객체를 참조만 가능
		
		rc = new Television();
		// Television 객체 생성
		rc.turnOn();
		// TV를 켭니다
		
		rc = new Audio();
		// Audio 객체 생성
		rc.turnOn();
		// Audio를 켭니다
	}
}

상수 필드

  • 클래스에서의 상수 public static final 특성을 가지는 불변의 상수 필드를 멤버로 가질 수 있음
  • 인터페이스에서는 public static final을 생략할 수 있음!
  • 하지만 상수명은 대문자로만 작성! 다른 단어로 구성할 때에는 언더바(_)로 연결
package ch08.sec03;

public interface RemoteControl {
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
	// 인터페이스에서는 private static 생략이 가능
	// 상수를 입력할 때에는 대문자와 언더바(_) 조합으로 작성
}

추상 메소드

  • 리턴 타입, 메소드명, 매개변수로만 이루어져있고, 중괄호 {}를 붙이지 않는 메소드
  • public abstract를 생략하더라도 컴파일 과정에서 자동으로 붙음
  • 추상 메소드는 객체 A가 인터페이스를 통해 어떻게 메소드를 호출할 수 있는지 방법을 알려주는 역할
  • 인터페이스는 코드의 재사용성과 관련이 없다 (각자 따로 구현해줘야 함)

인터페이스

package ch08.sec04;

public interface RemoteControl {
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
	// 상수 선언
	
	void turnOn();
	void turnOff();
	void setVolume(int volume);
	// 메소드 선언 시 추상 메소드와 동일하게 메소드 선언부만 작성
	// 실행문 동작은 구현 객체에서 함
    // 접근 방식이 없을 경우 public의 역할을 함
}

구현 클래스 (Television)

package ch08.sec04;

public class Television implements RemoteControl{
	// 인터페이스를 불러오기 위해서는 implements 키워드 사용
	// 인터페이스에서 추상 메소드를 사용하였음
	// 추상 메소드를 가지는 인터페이스를 사용하는 클래스에서 
	// 추상 메소드를 다 선언하지 않으면 오류가 남
	
	private int volume;
	
	public void turnOn() {
		System.out.println("TV를 켭니다");
	}
	
	public void turnOff() {
		System.out.println("TV를 끕니다");
	}
	
	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("현재 TV 볼륨");
	}
}

구현 클래스 (Audio)

package ch08.sec04;

public class Audio implements RemoteControl{
		// 인터페이스를 불러오기 위해서는 implements 키워드 사용
		// 인터페이스에서 추상 메소드를 사용하였음
		// 추상 메소드를 가지는 인터페이스를 사용하는 클래스에서 
		// 추상 메소드를 다 선언하지 않으면 오류가 남
		
		private int volume;
		
		public void turnOn() {
			System.out.println("Audio를 켭니다");
		}
		
		public void turnOff() {
			System.out.println("Audio를 끕니다");
		}
		
		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 볼륨");
		}
}

실행 클래스

package ch08.sec04;

public class RemoteControlExam {
	public static void main(String[] args) {
		RemoteControl rc;
		// 인터페이스는 직접 객체 생성이 불가능함
		// 따라서 참조변수 형태로만 선언이 가능
		
		// 구현 객체를 생성하여 인터페이스 참조변수에 대입하여 사용
		rc = new Television();
		rc.turnOn();
		rc.setVolume(5);
		rc.turnOff();
		// Television 객체에서 재정의한 메소드가 실행됨
		
		rc = new Audio();
		rc.turnOn();
		rc.setVolume(5);
		rc.turnOff();
		// Audio 객체에서 재정의한 메소드가 실행됨
	}
}

디폴트 메소드

  • 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있음
  • 실행문을 가진 메소드를 인터페이스에서 선언하기 위해서는 리턴 타입 앞에 default 키워드를 선언해줘야 함
  • default 메소드에서 default 접근 권한이 명시되어 있지 않으면 public의 기능을 함!
  • 자바 1.8 버전부터 추가됨!

인터페이스

package ch08.sec05;

public interface RemoteControl {
	// 상수 선언
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
	
	// 추상 메소드 선언
	void turnOn();
	void turnOff();
	void setVolume(int volume);
	
	// 디폴트 메소드 : 인터페이스에서 메소드 실행 코드를 작성하기 위함
	// 앞에 default를 꼭 적어주고, 중괄호(실행 블록)를 꼭 선언해줘야함
	// 접근 방식은 default 앞에 적는데, 없으면 public!
	default void setMute(boolean mute) {
		if(mute) {
			System.out.println("무음 처리합니다");
			setVolume(MIN_VOLUME);
		}
		else {
			System.out.println("무음 해제합니다");
		}
	}
}

구현 클래스에서 디폴트 메소드 재정의

package ch08.sec05;

public class Audio implements RemoteControl{
		// 인터페이스를 불러오기 위해서는 implements 키워드 사용
		// 인터페이스에서 추상 메소드를 사용하였음
		// 추상 메소드를 가지는 인터페이스를 사용하는 클래스에서 
		// 추상 메소드를 다 선언하지 않으면 오류가 남
		
		private int volume;
		
		public void turnOn() {
			System.out.println("Audio를 켭니다");
		}
		
		public void turnOff() {
			System.out.println("Audio를 끕니다");
		}
		
		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;
				// 해당 클래스의 volume 필드에 저장된 값을 위의 memoryVolume 필드에 저장 
				System.out.println("무음 처리함");
				setVolume(RemoteControl.MIN_VOLUME);
				// 인터페이스에서 선언한 상수 호출
			}
			else {
				System.out.println("무음 해제합니다");
				setVolume(this.memoryVolume);
				// 이전에 설정한 볼륨값 기억
			}
		}
}

실행 클래스

package ch08.sec05;

public class RemoteControlExam {
	public static void main(String[] args) {
		RemoteControl rc;
		// 인터페이스는 객체로 생성 불가능, 참조변수로만 가능
		
		// 인터페이스 참조변수에 Television 객체 대입
		rc = new Television();
		rc.turnOn();
		rc.setVolume(5);
		
		// 디폴트 메소드를 호출함
		rc.setMute(true);
		rc.setMute(false);
		
		// 인터페이스 참조 변수에 Audio 객체 대입
		rc = new Audio();
		rc.turnOn();
		rc.setVolume(6);
		
		// 디폴트 메소드를 호출함
		rc.setMute(true);
		rc.setMute(false);
	}
}

정적 메소드

  • 구현 객체 없이 인터페이스만으로 호출 가능
  • 정적 실행부를 사용할 때 상수 필드, 정적 상수 필드 및 정적 메소드만 사용 가능함!
  • 추상, 디폴트, private 메소드 등 호출 및 일반 멤버 변수, this 사용 불가능
  • public은 생략 가능 (생략해도 public의 기능을 함)

인터페이스

package ch08.sec06;

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("무음 해제합니다");
		}
	}
	
	// 정적 메소드
	// 정적 메소드에는 정적 필드만 사용 가능
    // 접근 권한 명시되어있지 않으면 public
	static void changeBattery() {
		System.out.println("리모콘 건전지를 교체합니다.");
	}
}

실행 클래스

package ch08.sec06;

public class RemoteControlExam {
	public static void main(String[] args) {
		RemoteControl rc;
		// 인터페이스는 객체로 생성 불가능, 참조변수로만 가능
		
		// 인터페이스 참조변수에 Television 객체 대입
		rc = new Television();
		rc.turnOn();
		rc.setVolume(5);
		
		// 디폴트 메소드를 호출함
		rc.setMute(true);
		rc.setMute(false);
		
		// 인터페이스 참조 변수에 Audio 객체 대입
		rc = new Audio();
		rc.turnOn();
		rc.setVolume(6);
		
		// 디폴트 메소드를 호출함
		rc.setMute(true);
		rc.setMute(false);
		
		// 정적 메소드
		// 정적 메소드는 인터페이스 구현 객체 없이 바로 생성 가능
		RemoteControl.changeBattery();
	}
}

private 메소드

  • 인터페이스 외부에서 접근할 수 없는 메소드
  • 디폴트 메소드 안에서만 호출 가능
  • private 정적 메소드는 정적 메소드 안에서도 호출 가능
  • 사용 거의 안함,,

인터페이스

package ch08.sec07;

public interface Service {
	// 디폴트 메소드
	default void defaultMethod1() {
		System.out.println("defaultMethod1 종속 코드");
		defaultCommon();
		// private 메소드를 선언하여 외부에서도 사용 가능하게 함
	}
	
	default void defaultMethod2() {
		System.out.println("defaultMethod2 종속 코드");
		defaultCommon();
	}
	
	// private 메소드는 외부에서 사용이 불가능
	// 따라서 위의 디폴트 메소드를 통해 해당 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 메소드는 외부에서 사용이 불가능
	// 따라서 위의 정적 메소드를 통해 해당 private 메소드를 실행 할 수 있음
	// 정적 메소드이기 때문에 static을 꼭 붙여주기 
	private static void staticCommon() {
		System.out.println("staticMethod 중복 코드A");
		System.out.println("staticMethod 중복 코드B");
	}
}

다중 인터페이스

  • 구현 객체는 여러 개의 인터페이스를 통해 구현 객체를 사용할 수 있음
  • 구현 클래스는 인터페이스 A와 인터페이스 B를 implements 뒤에 쉼표로 구분해서 작성해, 모든 인터페이스가 가진 추상 메소드를 재정의
  • 부모 인터페이스가 두 개 이상일 수 있음!

인터페이스

package ch08.sec08;

public class SmartTelevision implements RemoteControl, Searchable{
	// 인터페이스를 두 개 이상 가져옴으로서 다중 인터페이스 구현이 가능하다
	// 가져오는 인터페이스들의 추상 메소드를 모든 불러와야 에러가 안뜸
	
	// RemoteControl 인터페이스의 메소드 선언
	@Override
	public void turnOn() {
		System.out.println("TV를 켭니다");
	}
	
	// RemoteControl 인터페이스의 메소드 선언
	@Override
	public void turnOff() {
		System.out.println("TV를 끕니다");
	}
	
	// Searchable 인터페이스의 메소드 선언
	@Override
	public void search(String url) {
		System.out.println(url + "을 검색합니다");
	}
}

인터페이스 상속

  • 인터페이스도 다른 인터페이스를 상속할 수 있으며, 다중 상속을 허용한다.
  • 인터페이스끼리 상속할 경우 클래스와 같이 extends를 사용하여 뒤에 키워드를 붙여줌
  • 자식 인터페이스에서 부모 인터페이스의 메소드를 재정의할 수 있음

인터페이스 A

package ch08.sec09;

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

인터페이스 B

package ch08.sec09;

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

인터페이스 C (인터페이스 A와 B를 상속받음)

package ch08.sec09;

public interface InterfaceC extends InterfaceA, InterfaceB {
	// 인터페이스는 상속받을 때 다중 상속을 허용함
	void methodC();
}

인터페이스 구현 객체

package ch08.sec09;

public class InterfaceCImpl implements InterfaceC{
	// InterfaceC의 인터페이스를 사용
	// 상속 받은 인터페이스의 추상 메소드의 실행문을 만들어야함
	
	public void methodA() {
		System.out.println("A");
	}
	
	public void methodB() {
		System.out.println("B");
	}
	
	public void methodC() {
		System.out.println("C");
	}
}

실행 클래스

package ch08.sec09;

public class ExtendsExam {
	public static void main(String[] args) {
		InterfaceCImpl impl = new InterfaceCImpl();
		// 인터페이스의 구현 객체를 생성
		
		// 인터페이스A의 참조변수에 구현 객체를 대입함
		InterfaceA ia = impl;
		ia.methodA();
		System.out.println();
		
		// 인터페이스B의 참조변수에 구현 객체를 대입함
		InterfaceB ib = impl;
		ib.methodB();
		System.out.println();
		
		// 인터페이스C의 참조변수에 구현 객체를 대입함
		// 상속받은 모든 메소드를 불러올 수 있음
		InterfaceC ic = impl;
		ic.methodA();
		ic.methodB();
		ic.methodC();
	}
}

타입 변환

자동 타입 변환

  • 자동적으로 타입 변환이 일어나는 것
  • 부모 클래스가 인터페이스를 구현하고 있으면 자식 클래스도 인터페이스 타입으로 변환 가능

강제 타입 변환

  • 캐스팅 기호를 사용해서 인터페이스 타입을 구현 클래스 타입으로 변환 시키는 것
  • 구현 객체가 인터페이스 타입으로 변환되면 인터페이스에 선언된 메소드만 사용 가능

인터페이스

package ch08.sec10;

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

구현(자식) 클래스

package ch08.sec10;

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

실행 클래스

package ch08.sec10;

public class CastingExample {
	public static void main(String[] args) {
		Vehicle vehicle = new Bus();
		// Vehicle 인터페이스 참조변수에 Bus 객체를 생성함
		// 자식(구현) 객체가 부모(인터페이스)에 대입하는 것이기 때문에 자동적으로 형 변환이 일어남
		
		vehicle.run();
		// 인터페이스 참조변수에 자식(구현) 객체를 대입했기 때문에
		// Bus 객체에 있는 run 메소드가 실행됨
		
		// vehicle.checkFare();
		// 하지만 vehicle은 인터페이스 참조변수여서 인터페이스 안에 있는 내용만 참조 가능
		// checkFare 메소드는 인터페이스 내부에 없는 메소드이기 때문에 불러올 수 없음
		
		
		Bus bus = (Bus) vehicle;
		// vehicle은 Bus 객체의 부모 개념의 Vehicle 인터페이스를 참조하는 변수이므로
		// 자식(구현) 객체의 메소드를 호출하기 위해서는 해당 타입으로 변경해줘야함
		// 따라서 강제 형변환을 통해 자식객체의 타입으로 변환해야함
		// Vehicle 인터페이스는 Bus 객체를 참조하기 때문에 강제 형변환이 가능
		
		bus.run();
		// Bus 객체의 메소드가 실행됨
		bus.checkFare();
		// Bus 객체 타입으로 변환되었기 때문에 해당 객체의 메소드를 불러올 수 있음
	}

}

다형성

  • 사용 방법은 동일하지만 다양하게 결과가 나오는 것을 의미
  • 인터페이스 역시 다형성을 구현하기 위해 재정의와 자동 타입 변환 기능을 이용한다
  • 매개 변수 타입을 인터페이스로 선언하면 메소드 호출 시 다양한 구현 객체를 대입할 수 있음

필드의 다형성 예제

인터페이스

package ch08.sec11.exam01;

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

구현 클래스 (KumhoTire)

package ch08.sec11.exam01;

public class KumhoTire implements Tire{
	// Tire 인터페이스를 불러옴 
	
	// Tire에서 선언한 추상메소드를 재정의해 동작을 구현함
	@Override
	public void roll() {
		System.out.println("금호타이어가 굴러갑니다");
	}
}

구현 클래스 (HankookTire)

package ch08.sec11.exam01;

public class HankookTire implements Tire{
	// Tire 인터페이스를 불러옴 
	
	// Tire에서 선언한 추상메소드를 재정의해 동작을 구현함
	@Override
	public void roll() {
		System.out.println("한국타이어가 굴러갑니다");
	}
}

Car 클래스

package ch08.sec11.exam01;

public class Car {
	Tire tire1 = new HankookTire();
	Tire tire2 = new HankookTire();
	// Tire 인터페이스 참조변수에 HankookTire 객체를 생성하여 값을 대입함
	
	void run() {
		tire1.roll();
		tire2.roll();
		// HankookTire 객체의 roll 메소드를 실행
	}
}

실행 클래스

package ch08.sec11.exam01;

public class CarExample {
	public static void main(String[] args) {
		Car myCar = new Car();
		// Car 객체 생성
		
		myCar.run();
		// Car의 메소드에는 한국타이어가 설정되있음
		
		myCar.tire1 = new KumhoTire();
		myCar.tire2 = new KumhoTire();
		// Car 클래스 안에 선언되어 있는 Tire 인터페이스 참조변수 필드 두개에
		// 금호타이어 객체를 생성하여 타이어를 교체함
		
		myCar.run();
		// tire1, tire2 참조변수에 금호타이어 객체를 생성하여 대입하였음
		// 따라서 금호타이어에서 재정의된 roll 메소드가 실행됨
	}
}

매개 변수의 다형성

인터페이스

package ch08.sec11.exam02;

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

Driver 클래스

package ch08.sec11.exam02;

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

Bus 클래스 (구현 클래스)

package ch08.sec11.exam02;

public class Bus implements Vehicle{
	// Vehicle 인터페이스를 불러옴
	
	// 인터페이스의 run 메소드를 해당 클래스에서 재정의
	// 추상 메소드이기 때문에 선언하지 않으면 오류
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}

Taxi 클래스 (구현 클래스)

package ch08.sec11.exam02;

public class Taxi implements Vehicle{
	// Vehicle 인터페이스를 불러옴
	
	// 인터페이스의 run 메소드를 해당 클래스에서 재정의
	// 추상 메소드이기 때문에 선언하지 않으면 오류
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
}

실행 클래스

package ch08.sec11.exam02;

public class DriverExam {
	public static void main(String[] args) {
		// Driver 객체 생성
		Driver driver = new Driver();
		
		// Bus와 Taxi 객체 생성 및 참조변수 선언
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		
		// Driver 객체의 drive 메소드에 Bus와 Taxi 참조변수를 대입함
		// drive 메소드의 매개변수는 Vehicle 타입인데, Bus와 Taxi 객체가 
		// Vehicle 인터페이스를 참조하므로 자동 형변환이 일어남
		// 또한 drive 메소드 내부에 있는 run 메소드가 해당 객체에서 재정의 되어서
		// Bus 객체와 Taxi 객체에 있는 run 메소드 값이 각각 나옴
		driver.drive(bus);
		driver.drive(taxi);
	}
}

instanceof 연산자

  • 인터페이스에서도 객체 타입을 확인하기 위해 instanceof 연산자를 사용 가능
  • Java 12 이후부터는 연산자의 값이 true이면 우측 타입의 변수에 대입 가능

인터페이스

package ch08.sec12;

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

구현 클래스 Bus

package ch08.sec12;

public class Bus implements Vehicle{
	// Vehicle 인터페이스를 참조
	// 참조하는 인터페이스에 추상 메소드가 있기 때문에
	// 해당 클래스에 추상 메소드를 모두 선언해야함
	
	// 인터페이스에 선언된 run() 메소드 재정의
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
	
	// 해당 클래스에서 자체적으로 만든 메소드 (승차요금 확인)
	public void checkFare() {
		System.out.println("승차요금을 체크합니다.");
	}
}

구현 클래스 Taxi

package ch08.sec12;

public class Taxi implements Vehicle{
	// Vehicle 인터페이스를 참조
	// 참조하는 인터페이스에 추상 메소드가 있기 때문에
	// 해당 클래스에 추상 메소드를 모두 선언해야함
	
	// 인터페이스에 선언된 run() 메소드 재정의
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
}

실행 클래스

package ch08.sec12;

public class InstanceofExam {
	public static void main(String[] args) {
		Taxi taxi = new Taxi();
		Bus bus = new Bus();
		// Taxi와 Bus 객체 생성
		
		ride(taxi);
		System.out.println();
		ride(bus);
	}
	
	// Vehicle 인터페이스 타입을 매개변수로 가지는 ride 메소드 생성
	// 해당 클래스 실행문에서 사용을 위해 static(정적) 메소드 선언 
	// -> 위의 실행문이 static void main으로 시작하기 때문 
	// 해당 매개변수는 인터페이스를 참조하는 구현객체 타입의 형태로 대입되는데 이 과정에서 자동형변환이 일어남
	public static void ride(Vehicle vehicle) {
		
		// instanceof : 해당 타입이 자식 객체의 타입으로 변환할 수 있는지 여부를 확인하고
		// 변환이 가능할 경우 변환될 타입의 변수에 객체 저장
		// Bus 객체에서 Vehicle 인터페이스를 참조함으로서 강제형변환 가능
		if(vehicle instanceof Bus bus) {
			// Bus 객체 타입으로 강제 형변환 후 bus 참조변수에 이를 대입
			// checkFare 메소드 실행
			bus.checkFare();
		}
		// Taxi나 Bus 객체에서 재정의된 run 메소드 실행
		vehicle.run();
	}
}

sealed 인터페이스

  • 무분별한 자식 생성을 막기 위한 봉인 인터페이스
  • permits 뒤에 상속 가능한 인터페이스 지정
  • non-sealed를 사용해 봉인 해제 후 상속 가능
profile
끄작끄작

0개의 댓글