EunJng

[Java] 이것이 자바다 Ch.08 본문

STUDY/JAVA

[Java] 이것이 자바다 Ch.08

Eunjng 2024. 1. 19. 21:01

'이것이 자바다' 교재 및 강의를 참고하여 정리한 내용입니다.

Ch.08 인터페이스

인터페이스 역할

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

  • 객체 A가 B를 직접 사용한다면 B가 C로 변경될 경우 A의 소스 코드를 B에서 C로 변경하는 작업이 필요하지만, 인터페이스를 사용하면 B가 C로 변경된 것에는 관심이 없다.
  • 인터페이스는 다형성 구현에 주된 기술로 이용된다.

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

인터페이스 선언

  • class 키워드 대신 interface 키워드를 사용
  • 중괄호 안에 인터페이스가 가지는 멤버들을 선언
public interface 인터페이스명 {
    // public 상수 필드
    // public 추상 메소드
    // public 디폴트 메소드
    // public 정적 메소드
    // private 메소드
    // private 정적 메소드
}

구현 클래스 선언

  • 위 그림에서 객체 B를 인터페이스를 구현한 객체라 한다. 구현 객체는 인터페이스를 구현하고 있음을 선언부에 명시해야 한다
    • public class B implements 인터페이스명 {...}

변수 선언과 구현 객체 대입

  • 인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있다. 인터페이스는 참조 타입에 속하므로 인터페이스 변수에는 객체를 참조하고 있지 않다는 뜻으로 null 대입 가능
  • 인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현 객체의 번지를 대입해야 한다.
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를 켭니다.");
    }
}


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을 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.
  • 상수명은 대문자로 작성하되, 언더바로 연결하는 것이 관례
    • 상수는 구현 객체와 관련 없는 인터페이스 소속 멤버이므로 인터페이스로 바로 접근해서 상수값을 읽을 수 있다.

추상 메소드

  • 인터페이스는 public 추상 메소드를 멤버로 가질 수 있다. public abstract 생략 가능
    • [public abstract] 리턴타입 메소드명(매개변수, ...);
  • 구현 클래스에서 추상 메소드를 재정의할 때, 인터페이스의 추상 메소드는 기본적으로 public 접근 제한을 갖기 때문에 public보다 더 낮은 접근 제한으로 재정의할 수 없다.

디폴트 메소드

  • 인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있다. 추상 메소드는 실행부({})가 없지만 디폴트 메소드는 실행부가 있고, default 키워드가 리턴 타입 앞에 붙는다.
    • [public] default 리턴타입 메소드명(매개변수, ...) {...}
  • 디폴트 메소드는 구현 객체가 필요한 메소드로, 구현 객체를 인터페이스 변수에 대입하고 나서 호출해야 한다.
  • 디폴트 메소드를 재정의할 때는 public 접근 제한자를 반드시 붙여야 하고, default 키워드를 생략해야 한다.

정적 메소드

  • 정적 메소드는 구현 객체가 없어도 인터페이스만으로 호출 가능. public 생략 가능
    • [public | private] static 리턴타입 메소드명(매개변수, ...) {...}
  • 정적 메소드의 실행부에는 상수 필드를 제외한 추상 메소드, 디폴트 메소드, private 메소드 등을 호출할 수 없다. 구현 객체가 필요한 인스턴스 메소드이기 때문

private 메소드

  • private 메소드는 디폴트 메소드 안에서만 호출이 가능하지만, private 정적 메소드는 디폴트 메소드뿐 아니라 정적 메소드 안에서도 호출 가능
  • private 메소드의 용도는 디폴트와 정적 메소드들의 중복 코드를 줄이기 위함
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("무음 해제합니다.");
        }
    }

    // 정적 메소드
    static void changeBattery() {
        System.out.println("리모콘 건전지를 교환합니다.");
    }
}

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) {
        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);
    }
}

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 볼륨: " + this.volume);
    }

    // 필드
    privat int memoryVolume;

    // 디폴트 메소드 재정의
    @Override
    public void setMute(boolean mute) {
        if(mute) {
            this.memoryVolume = this.volume;
            setVolume(RemoteControl.MIN_VOLUME);
        } else {
            setVolume(this.memoryVolume);    // 원래 볼륨으로 복원
        }
    }
}


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);

        // Audio 객체를 생성하고 인터페이스 변수에 대입
        rc = new Audio();
        rc.turnOn();
        rc.setVolume(5);

        // 디폴트 메소드 호출
        rc.setMute(true);
        rc.setMute(false);

        // 정적 메소드 호출
        RemoteControl.changeBattery();
    }
}

다중 인터페이스 구현

  • public class 구현클래스명 implements 인터페이스A, 인터페이스B { //모든 추상 메소드 재정의 }
public interface RemoteControl {
    // 추상 메소드
    void turnOn();
    void turnOff();
}

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

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 + "을 검색합니다.");
    }
}

public class MultiInterfaceImplExample {
    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");
    }
}

인터페이스 상속

  • 클래스와 달리 인터페이스는 다중 상속 허용
    • public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 {...}
  • 자식 인터페이스의 구현 클래스는 자식 인터페이스의 메소드뿐만 아니라 부모 인터페이스의 모든 추상 메소드를 재정의해야 한다.
  • 구현 객체가 자식 인터페이스 변수에 대입되면 자식 및 부모 인터페이스의 추상 메소드를 모두 호출할 수 있으나, 부모 인터페이스 변수에 대입되면 부모 인터페이스에 선언된 추상 메소드만 호출 가능

타입 변환

  • 인터페이스의 타입 변환은 인터페이스와 구현 클래스 간에 발생
    • 인터페이스 변수에 구현 객체를 대입하면 구현 객체는 인터페이스 타입으로 자동 타입 변환
    • 인터페이스 타입을 구현 클래스 타입으로 변환시키려면 강제 타입 변환 필요

자동 타입 변환

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

강제 타입 변환

  • 구현클래스 변수 = (구현클래스) 인터페이스변수;
  • 구현 객체가 인터페이스 타입으로 자동 변환되면, 인터페이스에 선언된 메소드만 사용 가능

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

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

다형성

  • 인터페이스 역시 상속과 마찬가지로 다형성을 구현하기 위해 재정의와 자동 타입 변환 기능을 이용한다.
  • 인터페이스의 추상 메소드는 구현 클래스에서 재정의 해야 하며, 재정의되는 내용은 구현 클래스마다 다르다. 구현 객체는 인터페이스 타입으로 자동 타입 변환이 되고, 인터페이스 메소드 호출 시 구현 객체의 재정의된 메소드가 호출되어 다양한 실행 결과를 얻을 수 있다.

필드의 다형성

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

Car myCar = new Car();
myCar.tire1 = new KumhoTire();
  • tire1과 tire2 필드에 어떠한 타이어 구현 객체가 대입되어도 Car 객체는 타이어 인터페이스에 선언된 메소드만 사용하므로 문제가 되지 않는다.

매개변수의 다형성

  • 매개변수 타입을 인터페이스로 선언하면 메소드 호출 시 다양한 구현 객체를 대입할 수 있다.
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("택시가 달립니다.");
    }
}

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);
    }
}

객체 타입 확인

  • 메소드의 매개변수가 인터페이스 타입일 경우, 메소드 호출 시 매개값은 해당 인터페이스를 구현하는 모든 객체가 될 수 있다. 만약 매개값이 특정 구현 객체일 경우에만 강제 타입 변환을 하고 싶다면 instanceif 연산자를 사용해 매개값의 타입 검사
public void method( Vehicle vehicle) {
    if(vehicle instanceof Bus) {
        Bus bus = (Bus) vehicle;
        // bus 변수 사용
    }
}

// Java 12부터는 instanceof 연산의 결과가 true일 경우 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환이 필요 없다.
if(vehicle instanceof Bus bus) {
    // bus 변수 사용
}

봉인된 인터페이스

  • Java 15부터는 무분별한 자식 인터페이스 생성을 방지하기 위해 봉인된 인터페이스 사용 가능
  • Interface A의 자식 인터페이스는 InterfaceB만 가능하도록 InterfaceA를 봉인된 인터페이스로 선언
    • public sealed interface InterfaceA permits InterfaceB {...}
  • InterfaceB는 non-sealed 키워드로 선언하거나, sealed 키워드를 사용해 또 다른 봉인 인터페이스로 선언해야 한다.
    • public non-sealed interface InterfaceB extends InterfaceA {...}

'STUDY > JAVA' 카테고리의 다른 글

[Java] 이것이 자바다 Ch.10  (0) 2024.01.24
[Java] 이것이 자바다 Ch.09  (0) 2024.01.22
[Java] 이것이 자바다 Ch.07  (0) 2024.01.13
[Java] 이것이 자바다 Ch.06  (0) 2024.01.04
[Java] 이것이 자바다 Ch.05  (0) 2023.12.30