JAVA

JAVA 마스터가 되기 위한 여정-8

나는나는용 2023. 2. 27. 17:15
728x90

8. 인터페이스

8-1. 인터페이스

인터페이스: 객체의 사용 방법을 정의한 타입.

객체와 개발 코드의 접점
➥ 개발코드 ➟ 인터페이스메소드 ➟ 객체 메소드
➥ 개발코드는 객체 내부 구조 알 필요 없이 인터페이스 메소드만 알면 됨.

굳이 중간에 왜 인터페이스를 끼는걸까? 바로 객체 메소드 부르면 되잖아!?!
➥개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위함.

ex) 인터페이스1은 객체 메소드를 호출한다고 가정.
개발코드 ➟ 인터페이스1 ➟ 객체A : 리턴값 a
개발코드 ➟ 인터페이스1 ➟ 객체B : 리턴값 b

➠ 같은 인터페이스를 불렀음에도 객체만 갈아끼워도 리턴값 달라짐.
즉, 다양한 객체를 동일한 사용 방법으로 이용할 수 있음.

인터페이스 선언

물리적 형태는 클래스와 동일
(소스파일: .java , 컴파일러javac 통해 .class로 컴파일됨)

대 ! 신 !
클래스는 public class 어쩌구일테지만, 인터페이스는 다음의 형태이다.

[public] interface 인터페이스이름 { --- }

+) 인터페이스의 구성 멤버 : 상수 필드, 추상 메소드.
객체로 생성할 수 없기에 생성자 갖지 않음.

상수 필드 선언

인터페이스에 고정된 값. 실행 시에 데이터를 바꿀 수 없음.
public static final 생략해도 컴파일시 자동 첨가.

[public static final] 타입 상수이름 = 값;	// 초기값 지정 필수

추상 메소드 선언

인터페이스로 호출된 메소드는 최종적으로 객체에서 실행되므로, 인터페이스의 메소드는 추상 메소드로 선언되며, 이는 리턴타입, 메소드 이름, 매개 변수만 기술되고 중괄호가 붙지 않는 형태이다.
인터페이스에서 선언된 메소드는 모두 '추상메소드'이기에 public abstract를 생략해도 컴파일시 자동 첨가.

[public abstracct] 리턴타입 메소드이름 ( 매개변수, --- );

인터페이스 구현

객체는 인터페이스에 정의된 추상메소드의 실체메소드(추상메소드와 동일한 메소드이름,매개타입,리턴타입)를 가져야 함.

구현 클래스

'인터페이스 타입으로 사용할수 있음'을 알려주기 위해 보통의 클래스 선언에 추가적으로 implements 키워드 추가함.

pulbic class 구현클래스이름 implements 인터페이스이름{
//인터페이스에 선언된 추상메소드의 실체메소드 선언
}

+) 인터페이스의 모든 메소드는 기본적으로 public 접근제한을 갖기에, 구현클래스는 public보다 낮은 접근제한으로 작성 불가.

구현클래스 작성 완료 후 new 연산자로 객체 생성 가능하나 일반적인 방법은 인터페이스 사용 용도가 아님.

따라서,
인터페이스 변수선언한 뒤, 구현 객체대입해야 한다.

인터페이스 변수 = 구현 객체;

다중 인터페이스 구현 클래스

객체가 한 인터페이스에서만 사용될 수 있느냐?

아니다. 여러 인터페이스 타입으로 사용할 수 있다.

다만 구현 클래스 작성시 모든 인터페이스를 구현해야한다.

ex) 예시

public class 구현클래스이름 implements 인터페이스A, 인터페이스B{
//인터페이스A에 선언된 추상메소드의 실체메소드 선언
//인터페이스B에 선언된 추상메소드의 실체메소드 선언
}

인터페이스 사용

클래스 선언시 인터페이스는 다음으로 선언될 수 있다.
1. 필드, 생성자
2. 메소드의 매개변수, 생성자
3. 메소드의 로컬 변수

ex)

public class MyClass{
// 1.
// 필드
RemoteControl rc = new Television();
// 2.
// 생성자 -> 생성자의 매개값으로 구현객체 대입
MyClass(RemoteControl rc){
	this.rc=rc;
}
// 메소드
void methodA(){
	// 3.
	// 로컬 변수
	RemoteControl rc = new Audio();
}
// 4.
void methodB(RemoteControl rc){---}
}
// 1-1.
MyClass myClass = new MyClass();
myclass.rc.turnOn();
myclass.rc.setVolume(5);
// 2-1.
MyClass(RemoteControl rc){
	this.rc=rc;
	rc.turnOn();
	rc.setVolume(5);
}
// 2-2.
MyClass myClass = new MyClass(new Audio());
// 위와 같이 MyClass의 객체 myClass가 생성된 경우에는 Audio의 turnOn(),setVolume()메소드만 실행됨.
// 3-1.
void methodA(){
	RemoteControl rc = new Audio();
	rc.turnOn();
	rc.setVolume(5);
}
// 4-1.
void methodB(RemoteControl rc){
	rc.turnOn();
	rc.setVolume(5);
}
// 4-2.
MyClass myClass = new Myclass();
myClass.methodB(new Television());
//위와 같이 methodB메소드가 호출된 경우에는 Television의 turnOn(),setVolume()메소드만 실행됨.

전체 응용 예시
-RemoteControl 인터페이스

package sec01.exam04;
public interface RemoteControl {
	//상수
	public int MAX_VOLUME = 10;
	public int MIN_VOLUME = 0;
	//추상메소드
	public void turnOn();
	public void turnOff();
	public void setVolume(int volume);
}

-Television객체

package sec01.exam04;
public class Television implements RemoteControl {
	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 볼륨: " + this.volume);
	}
}

-Audio객체

package sec01.exam04;
public class Audio implements RemoteControl {
	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 볼륨: " + this.volume);
	}
}

-인터페이스 사용 클래스

package sec01.exma06;
import sec01.exam04.*;
public class MyClass {
	RemoteControl rc = new Television();
//생성자
	MyClass() {
	}
	MyClass(RemoteControl rc) {
		this.rc = rc;
		rc.turnOn();
		rc.setVolume(5);
	}
//메소드
	void methodA() {
		RemoteControl rc = new Audio();
		rc.turnOn();
		rc.setVolume(3);
	}
	void methodB(RemoteControl rc) {
		rc.turnOn();
		rc.setVolume(7);
	}
}

-실행 클래스

package sec01.exma06;
import sec01.exam04.*;
public class MyClassExample {
	public static void main(String[] args) {
		System.out.println("1)---------------------");		
		MyClass myClass1 = new MyClass();
		myClass1.rc.turnOn();
		myClass1.rc.setVolume(5);	
		System.out.println("2)---------------------");
		MyClass myClass2 = new MyClass(new Audio());		
		System.out.println("3)---------------------");
		MyClass myClass3 = new MyClass();
		myClass3.methodA();	
		System.out.println("4)---------------------");
		MyClass myClass4 = new MyClass();
		myClass4.methodB(new Television());
	}
}

실행결과

8-2. 타입 변환과 다형성

다형성 구현 위해 메소드 재정의와 타입 변환이 필요함.
상속: 같은 종류의 하위 클래스를 만듦.
인터페이스: 사용 방법이 동일한 클래스를 만듦.

이 둘은 개념상 차이가 있으나 다형성 구현 방법은 비슷함.

인터페이스의 다형성
프로그램 개발 시, 인터페이스 사용해서 메소 호출하도록 코딩했다면,
프로그램 소스 코드 변함 없이, 구현 객체의 교체만으로 다양한 실행 결과 도출 가능.

자동 타입 변환

자동 타입 변환: 프로그램 실행 도중 자동적으로 타입 변환이 일어나는 것. ex) 구현객체가 인터페이스타입으로 변환

필드의 다형성과 매개변수의 다형성을 구현할 수 있음.
-> 이들의 변수 타입을 인터페이스로 선언->다양한 구현객체 대입->다양한 실행결과

인터페이스 변수 = 구현객체;

인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면, 자식객체도 인터페이스 타입으로 자동타입변환 가능.

필드의 다형성

ex) 상속: 타이어 = 클래스 , 한국타이어&금호타이어 = 자식객체
인터페이스: 타이어 = 인터페이스, 한국&금호타이어 = 구현객체

구현 객체를 교체하더라도, Car객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 문제 발생하지 않는다.

필드의 다형성

void run(){
	frontLeftTire.roll();
    frontRightTire.roll();
    backLeftTire.roll();
    backRightTire.roll();
}

Car의 run()메소드를 수정하지 않아도, 객체 바꾸기만 해도 되므로, 다양한 roll()메소드의 실행결과를 얻을 수 있다.

Tire 인터페이스

package sec02.exam01;
public interface Tire {
	public void roll();
}

구현객체 1 - 한국타이어

package sec02.exam01;
public interface Tire {
	public void roll();
}

구현객체 2 - 금호타이어

package sec02.exam01;
public class KumhoTire implements Tire{
	@Override
	public void roll() {
		System.out.println("금호타이어가 굴러갑니다.");
	}
}

Car 클래스

package sec02.exam01;
public class Car {
	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();
	}
}

실행클래스 (객체 갈아끼우기 전&후)

package sec02.exam01;
public class CarExample {
	public static void main(String[] args) {
		Car myCar = new Car();		
		myCar.run();
		myCar.frontLeftTire = new KumhoTire();
		myCar.frontRightTire = new KumhoTire();	
		myCar.run();
	}
}

실행결과

매개변수의 다형성

자동타입변환은 필드의 값을 대입할 때에도 발생하나, 주로 메소드를 호출할 때 많이 발생함.

매개값 다양화를 위해
-상속:
매개변수를 부모타입으로 선언, 호출할 때 자식 객체 대입.
-인터페이스:
매개변수를 인터페이스타입으로 선언, 호출할 때 구현객체 대입.

ex)
Bus가 Vehicle인터페이스의 구현클래스

public class Driver{
	public void drive(Vehicle vehicle){
    	vehicle.run();
    }
}
public interface Vehicle{
	public void run();
}
Driver driver = new Driver();
Bus bus = new Bus();
driver.drive(bus);

라고 할 때,

Driver객체인 driver의 drive함수에 Vehicle을 구현한 Bus의 객체인 bus를 매개변수로 넘겨주는데,
Driver클래스에서 drive메서드는 Vehicle타입을 매개변수로 받는다고 되어져있다.
따라서, Vehicle vehicle = bus라는 자동타입변환이 발생했다.

매개변수의 타입 인터페이스일 경우 어떠한 구현객체도 매개값으로 사용할 수 있고, 어떤 구현객체가 제공되느냐에 따라 메소드의 실행결과가 다양해진다.

+)Tip
매개변수의 다형성을 위해, 인터페이스는 메소드의 매개변수로 많이 등장함. (메소드 호출시 여러 종류의 구현객체를 매개값으로 줄 수 있기때문)

Vehicle 인터페이스

package sec02.exam02;
public interface Vehicle {
	public void run();
}

구현객체1 - Bus

package sec02.exam02;
public class Bus implements Vehicle{
	@Override
	public void run(){
		System.out.println("버스가 달립니다.");
	}
}

구현객체2 - Taxi

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

Driver클래스

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

실행클래스

package sec02.exam02;
public class DriverExample {
	public static void main(String[] args) {
		Driver driver = new Driver();
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		driver.drive(bus);
		driver.drive(taxi);
	}
}

<최종결론>
drive는 Vehicle타입만 매개변수로 받는데, bus와 taxi는 Vehicle의 구현객체들의 객체이다. 따라서 이들을 전달해주면, 이들은 저절로 Vehicle타입으로 자동타입변환이 된다.

강제타입변환

구현 객체가 인터페이스타입으로 자동타입변환하면, 인터페이스에 선언된 메소드만 사용가능함.
이럴 경우, 구현클래스에 선언된 필드와 메소드를 사용해야 할 때, 강제타입변환을 하여 다시 구현클래스타입으로 변환한 뒤 사용 가능.

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

인터페이스

package sec02.exam03;
public interface Vehicle {
	public void run();
}

구현객체 - Bus

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

실행클래스

package sec02.exam03;
public class VehicleExample {
	public static void main(String[] args) {
		Vehicle vehicle = new Bus();
		vehicle.run();
		//vehicle.checkFare(); -> Vehicle인터페이스에는 checkFare()메소드가 없음.
		Bus bus = (Bus) vehicle;
		bus.run();
		bus.checkFare();
	}
}

실행결과

객체 타입 확인

강제타입변환은 구현객체가 인터페이스타입으로 변환된 상태에서 가능한데, 어떤 구현객체가 변환됐는지 알 수 없는 상태에서 무작정 변환하려들다가 예외가 발생할 수 있다.
상속에서는 객체타입을 확인하기 위해 instanceof 연산자를 사용했는데, 이는 인터페이스에서도 사용된다.
ex) 안전한 강제타입변환 방법

if(vehicle instanceof Bus){
	Bus bus = (Bus) vehicle;
}

강제타입변환의 예제를 안전한 방법으로 보완해보자.

package sec02.exam04;
import sec02.exam03.*;
public class Driver {
	public void drive(Vehicle vehicle) {
		if(vehicle instanceof Bus) {
			Bus bus = (Bus) vehicle;
			bus.checkFare();
		}
		vehicle.run();
	}
}

번외 - 인터페이스 상속

인터페이스는 다중 상속을 허용함.

public interface 하위인페 extends 상위인페1, 상위인페2{---}

하위인터페이스의 구현객체는 '하위인터페이스의 메소드 + 상위인터페이스들의 메소드' 에 대한 실체 메소드를 갖고있어야 한다.
후에, 구현클래스로부터 객체를 생성한 후 하위/상위 인터페이스 타입으로 변환이 가능함.

이 때,
하위인터페이스로 타입변환되면 상위/하위인터페이스에 선언된 모든 메소드를 사용할 수 있으나,
상위인터페이스로 타입변환되면, 상위인터페이스에 선언된 메소드만 사용 가능하다.

상위인페1 - A

package sec02.exam05;
public interface InterfaceA {
	public void methodA();
}

상위인페2 - B

package sec02.exam05;
public interface InterfaceB {
	public void methodB();
}

하위인페 - C

package sec02.exam05;
public interface InterfaceC extends InterfaceA, InterfaceB{
	public void methodC();
}

하위인페C 구현

package sec02.exam05;
public class ImplementationC implements InterfaceC{
	public void methodA() {
		System.out.println("ImplementationC - methodA() 실행");
	}
	public void methodB() {
		System.out.println("ImplementationC - methodB() 실행");
	}
	public void methodC() {
		System.out.println("ImplementationC - methodC() 실행");
	}
}

실행클래스

package sec02.exam05;
public class Example {
	public static void main(String[] args) {
		ImplementationC impl = new ImplementationC();
		InterfaceA ia = impl;
		ia.methodA();
		System.out.println();
		InterfaceB ib = impl;
		ib.methodB();
		System.out.println();
		InterfaceC ic = impl;
		ic.methodA();
		ic.methodB();
		ic.methodC();
		System.out.println();
	}
}

실행결과

728x90