8장 인터페이스

→ 인터페이스를 이용한 '다형성'을 더 많이 사용한다.

 

8.1 인터페이스 역할

두 객체를 연결하는 역할.

 

객체 A  ↔  인터페이스  ↔  객체 B, 객체 C, ...

ex) 사람  ↔  리모콘  ↔  TV, Audio, ...

 

 

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

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

접근 제한자로 default, public을 붙일 수 있다.

 

● 인터페이스 선언

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

→ 거의 99%로 상수 필드와 추상 메소드만으로 사용한다.

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

 

 

● 구현 클래스 선언

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

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

인터페이스를 구현한 클래스는 인터페이스에 정의된 추상 메소드를 재정의 해야한다.

 

● 변수 선언과 구현 객체 대입하기

// main 메소드 안
RemoteControl rc;	// 인터페이스 타입 변수 선언
rc = new Television();  // 인터페이스 변수에 구현 객체 대입
rc.turnOn();  // Television에서 정의된 turnOn() 메소드 호출

만약 Television이 RemoteControl 인터페이스를 구현한 클래스가 아니라면

변수 rc에 Television 객체를 생성할 수 없다!

 

 

8.3 상수 필드

인터페이스에 선언된 필드는 모두 [ public static final ] 특성을 갖기 때문에

[ public static final ]을 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.

상수명은 대문자로 작성, 서로 다른언어로 구성되어 있을 경우 언더바(_)로 연결하는 것이 관례.

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

상수는 구현 객체와 관련 없는 인터페이스 소속 멤버.

인터페이스로 바로 접근하여 상수값을 읽을 수 있다.

ex) RemoteControl.MAX_VOLUME;

 

 

8.4 추상 메소드

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

추상 메소드는 리턴타입, 메소드명, 매개변수만 기술된다.

[ public abstract ] 를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.

위의 8.2절에서 쓴 것처럼 사용.

 

 

8.5 디폴트 메소드 (이해만)

인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있다.

추상 메소드는 실행 코드 X

default 키워드가 리턴 타입 앞에 붙어야 한다.

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() 디폴트 메소드를 선언한 것.

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

// main 메소드 안
RemoteControl rc;

rc = new Television();	// 구현 객체 대입
rc.turnOn();
rc.setVolume(5);

이처럼 구현 객체를 대입받고 setMute()를 호출할 수 있다는 것이다.

 

 

8.6 정적 메소드 (이해만)

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

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

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

 

선언 방법은 클래스 정적 메소드와 완전 동일.

단, [ public | private ]을 생략하더라도 컴파일 과정에서 자동으로 붙는다.

 

 

8.7 private 메소드 (이해만)

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

위의 멤버들은 public을 생략하더라도 컴파일 과정에서 public 접근 제한자가 붙어 항상 외부에서 접근이 가능하다.

또한, 외부에서 접근할 수 없는 private 메소드 선언도 가능하다.

 

 

8.8 다중 인터페이스 구현

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

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

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

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

 

 

8.9 인터페이스 상속 (개념 이해만)

인터페이스도 다른 인터페이스를 상속할 수 있다.

클래스와는 달리 다중 상속을 허용한다. → 여러 상속을 받는 경우는 드물다.

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

자식 인터페이스의 구현 클래스는 자식 인터페이스의 메소드뿐만 아니라 부모 인터페이스의 모든 추상 메소드를 재정의해야 한다. 그리고 구현 객체는 자식 및 부모 인터페이스 변수에 대입될 수 있다.

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

 

 

8.10 타입 변환

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

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

반대로 인터페이스 타입을 구현 클래스 타입으로 변환시킬 수 있는데, 이때 강제 타입 변환이 필요.

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

※ 사전에 '자동 타입 변환'을 수행되어야 함

 

예시)

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

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

// main 메소드 안
Vehicle vehicle = new Bus();  // 인터페이스 변수에 자동 타입 변환으로 Bus객체가 생성

// 인터페이스를 통해 호출
vehicle.run();  // Vehicle 타입에 run() 추상 메소드가 있으니 사용가능

// 강제 타입 변환 후 호출
Bus bus = (Bus) vehicle;  // 여기가 중요!
// 구현 객체를 자동 타입 변환으로 받은 vehicle은 강제 타입 변환이 가능해진다.
bus.run();
bus.checkFare();  // 강제 타입 변환으로 인해 인터페이스 내의 추상 메소드가 없고,
// Bus 구현 클래스 내의 checkFare() 메소드를 사용할 수 있게 된다!

이처럼 강제 타입 변환하면 인터페이스 내부에 없는 메소드를 타입 변환한 클래스 안의 메소드를 사용할 수 있게 된다.

 

 

8.11 다형성

현업에서 상속보다는 인터페이스를 통해 다형성을 구현하는 경우가 더 많다!

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

 

이번에는 부모 타입이 클래스 타입이 아니고 인터페이스라는 점이 차이점.

 

● 필드의 다형성

 

예시) 역시 코드로 보는 것이 이해하는데 더 도움이 된다.

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

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

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

public class Car {
	// 필드
    Tire tire1 = new HankookTire();  // 기본적으로 한국타이어를 씀
    Tire tire2 = new HankookTire();
    
    // 메소드
    void run() {
    	tire1.roll();
        tire2.roll();
    }
}

// main 메소드 안
Car myCar = new Car(); // 자동차 객체 생성
myCar.run();  // run() 메소드 실행

// 금호타이어로 교체
myCar.tire1 = new KumhoTire();  // 필드 타입이 Tire이기 때문에 자동 형 변환이 일어남
myCar.tire2 = new KumhoTire();

myCar.run();  // 금호타이어로 교체 후 run()메소드 실행

 

 

● 매개변수의 다형성

메소드 호출 시 매개값을 다양화하기 위해 상속에서 매개변수 타입을 부모 타입으로 선언하고 호출할 때 다양한 자식 객체를 대입했었다. (자동 타입 변환)

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

 

예시) 코드로 이해하자.

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

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

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

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

// main 메소드 안
Driver driver = new Driver();  // Driver 객체 생성

// Vehicle 구현 객체 생성
Bus bus = new Bus();
Taxi taxi = new Taxi();

// 매개값으로 구현 객체 대입 (다형성으로 실행 결과가 다름)
driver.drive(bus);
driver.drive(taxi);

 

 

8.12 객체 타입 확인

상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용.

인터페이스에서도 사용할 수 있다.

8.11절의 예시 코드에 이어서 이해하자.

// main 메소드가 같이 있는 클래스에서
public static void ride(Vehicle vehicle) {
    // 방법 1 (java 12 이전 버전)
    if(vehicle instanceof Bus) {  // 매개값이 Bus인 경우에
        Bus bus = (Bus) vehicle;  // 강제 타입 변환
        bus.checkFare();  // Bus 클래스의 checkFare() 메소드를 사용할 수 있다
    }

    // 방법 2 (java 12 이후 버전)
    if(vehicle instanceof Bus bus) {  // 곧바로 bus 변수를 이용 가능
        bus.checkFare();
    }
   
    vehicle.run();
}

// main 메소드 안
Taxi.taxi = new Taxi();  // 구현 객체 생성
Bus bus = new Bus();  // 구현 객체 생성

// ride() 메소드 호출 시 구현 객체를 매개값으로 전달
ride(taxi);
ride(bus);

주석을 통해 이해, 안되면 다시 책 복습!


※ 결론

상속과 인터페이스는 헷갈릴 수 있다. 여러번 복습하여 쓰임새와 차이점에 대해 생각해보자!

'JAVA' 카테고리의 다른 글

4일차 2024 - 2 - 29  (0) 2024.03.18
15일차 2024 - 3 - 18  (0) 2024.03.18
10일차 2024 - 3 - 11  (0) 2024.03.17
14일차 2024 - 3 - 15  (0) 2024.03.14
13일차 2024 - 3 - 14  (0) 2024.03.14

+ Recent posts