인터페이스

star_pooh·2024년 11월 13일
0

TIL

목록 보기
19/39
post-thumbnail

인터페이스의 역할

  • 두 객체를 연결해 주는 다리 역할로서 상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메소드를 구현해야 할 때 구현 클래스들의 동일한 사용 방법과 행위를 보장해줌
    • 인터페이스는 스펙이 정의된 메소드들의 집합
    • 인터페이스의 구현 클래스들은 반드시 정의된 메소드들을 구현해야함(구현 클래스들의 동일한 사용 방법과 행위를 보장해줄 수 있는 이유)
    • 이러한 특징으로 인해 인터페이스에 다형성 적용 가능

인터페이스 선언

  • interface 키워드를 사용하여 선언하며, 클래스와 마찬가지로 public, default 접근 제어자를 지정할 수 있음
public interface 인터페이스명 {
}

인터페이스 구성

  • 모든 멤버 변수는 public static final이어야하지만 생략 가능
  • 모든 메소드는 public abstract이어야하지만 생략 가능(static 메소드와 default 메소드는 예외)
  • 생략된 제어자는 컴파일러가 자동으로 추가
public interface 인터페이스명 { 
	public static final char A = 'A';
    static char B = 'B'; // public
    final char C = 'C'; // public static
    char D = 'D'; // public static final

    void turnOn(); // public abstract void turnOn();
}

인터페이스 구현

추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 때문에 클래스에 구현되어 생성됨

  • implements 키워드를 사용하여 구현
  • 인터페이스의 추상 메소드는 구현될 때 반드시 오버라이딩 되어야 함
  • 만약 인터페이스의 추상 메소드를 일부만 구현해야 한다면, 해당 클래스를 추상 클래스로 변경
public class 클래스명 implements 인터페이스명 { 
	// 추상 메소드 오버라이딩
	@Override
	public 리턴타입 메소드이름(매개변수, ...) {
		// 실행문
	}
}

인터페이스 상속

  • 인터페이스 간의 상속은 implements가 아닌 extends 키워드를 사용
  • 인터페이스는 클래스와 달리 다중 상속이 가능
    • 인터페이스는 실질적인 구현 없이 메소드에 대한 선언만 있기 때문에 메소드명이 동일하더라도 최종 구현은 객체에서 이루어질 것이기 때문에 가능
interface A {
    void a();
}
interface B {
    void b();
}
interface C extends A, B { }

public class Main implements C {
    @Override
    public void a() {
        System.out.println("A");
    }
   
	@Override
    public void b() {
		System.out.println("B");
    }
}
  • 인터페이스의 구현은 상속과 함께 사용 가능
interface A {
    void a();
}
interface B {
    void b();
}
interface C extends A, B {
}

class D {
    void d() {
        System.out.println("D");
    }
}

public class Main extends D implements C {
    @Override
    public void a() {
        System.out.println("A");
    }

    @Override
    public void b() {
        System.out.println("B");
    }

    @Override
    void d() {
        super.d();
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a(); // A
        main.b(); // B
        main.d(); // D
    }
}

디폴트 메소드와 static 메소드

디폴트 메소드

추상 메소드의 기본적인 구현을 제공하는 메소드

  • 메소드 앞에 default 키워드를 붙이며 {}블럭이 존재해야함
  • default 메소드의 접근 제어자는 public이며 생략 가능
  • 추상 메소드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요는 없음
interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
}

public class Main implements A {
    @Override
    public void a() {
        System.out.println("A");
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a(); // A
        // 오버라이딩 없이 사용 가능
        main.aa(); // AA
    }
}

개방-폐쇄 원칙 (OCP : Open Close Principle)
: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

클래스A...Z가 인터페이스A에 대해 구현하고 있을 때, 인터페이스A에 추상 메소드A가 추가된다고 생각해보자. 추상 메소드A를 사용하지 않는 모든 구현체에서도 구현이 필요하기 때문에 위 원칙을 위배하게 된다. (변경에 대해 열리게 되므로)

디폴트 메소드를 사용하게 되면 모든 구현체에서의 구현을 방지할 수 있기 때문에 위 원칙을 지킬 수 있게 된다.

⚠️ 주의할 점
디폴트 메소드는 인터페이스에만 추가된 개념이기 때문에 일반 추상 클래스에서는 사용할 수 없음

static 메소드

  • static의 특성 그대로 인터페이스의 static 메소드도 객체 없이 호출 가능
  • 선언 및 호출 방법은 클래스의 static 메소드와 동일
    • 접근 제어자를 생략하면 컴파일러가 public 추가
interface A {
    void a();
    static void aaa() {
        System.out.println("static method");
    }
}

public class Main implements A {
    @Override
    public void a() {
        System.out.println("A");
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a(); // A
        // static 메소드 aaa() 호출
        A.aaa(); // static method
    }
}

다형성

타입 변환

  • 자동 타입 변환
    • 인터페이스 변수 = 구현 객체는 자동으로 타입 변환이 일어남
interface A {}
class B implements A {}
class C extends B {}

public class Main {
    public static void main(String[] args) {
        // 인터페이스 A에 구현체 B 대입 (자동 타입 변환)
        A a1 = new B();
        // 인터페이스 A에 구현체 B를 상속받은 C 대입 (자동 타입 변환)
        A a2 = new C();
    }
}
  • 강제 타입 변환
    • 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수
interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }
    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}

public class Main {
    public static void main(String[] args) {
        // 인터페이스 A에 구현체 B 대입 (자동 타입 변환)
        A a1 = new B();
        a1.a();
        // a1.b(); // 인터페이스 A에 b()가 없으므로 에러 발생

        // 구현 객체 B로 강제 타입 변환
        B b = (B) a1;
        b.a();
        b.b(); // 강제 타입 변환으로 사용 가능

        // 인터페이스 A에 구현체 B를 상속받은 C 대입
        A a2 = new C();
        a2.a();
        // a2.b(); // 인터페이스 A에 b()가 없으므로 에러 발생
        // a2.c(); // 인터페이스 A에 c()가 없으므로 에러 발생

        // 구현 객체 C로 강제 타입 변환
        C c = (C) a2;
        c.a();
        c.b(); // 강제 타입 변환으로 사용 가능
        c.c(); // 강제 타입 변환으로 사용 가능
    }
}

인터페이스의 다형성

사용 방법은 동일하지만 다양한 특징과 결과를 가질 수 있는 것

// LG TV 구현체를 조작
MultiRemoteController mrc = new LgTv("LG");
mrc.turnOnOff();
mrc.channelDown();
// 조작 대상을 Samsung TV로 교체
mrc = new SamsungTv("Samsung");
mrc.turnOnOff();
mrc.channelUp();
  • 멀티 리모컨 인터페이스 변수 = TV 구현 객체를 선언하여 자동 타입 변환된 인터페이스 변수로 TV 구현 객체의 기능을 조작
  • TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 수정 작업 없이 그대로 기능 호출 가능
  • 멀티 리모컨으로 티비를 사용하는 방법은 동일하지만 어떤 TV 구현 객체냐에 따라 실행 결과가 다르게 나오는 것을 통해 다형성의 적용 확인 가능
// 매개변수와 반환타입 다형성 확인 메소드
default MultiRemoteController getTV(Tv tv) {
	// 리턴 타입은 인터페이스, 매개변수는 구현 객체이므로 자동 타입 변환됨
    if(tv instanceof SamsungTv) {
        return (SamsungTv) tv;
    } else if(tv instanceof LgTv){
        return (LgTv) tv;
    } else {
        throw new NullPointerException("일치하는 TV 없음");
    }
}
  • 인터페이스에서도 매개변수와 리턴 타입에서 다형성 적용이 가능

// 멀티 리모컨 인터페이스
public interface MultiRemoteController {
    void turnOnOff();
    void channelUp();
    void channelDown();

    // 매개변수와 리턴타입 다형성 확인 메소드
    default MultiRemoteController getTV(Tv tv) {
        if(tv instanceof SamsungTv) {
            return (SamsungTv) tv;
        } else if(tv instanceof LgTv){
            return (LgTv) tv;
        } else {
            throw new NullPointerException("일치하는 TV 없음");
        }
    }
}
// TV 추상 클래스
public abstract class Tv {
    private String company; // 티비 회사
    private int channel = 1; // 현재 채널 상태
    private boolean power = false; // 현재 전원 상태

    public Tv(String company) {
        this.company = company;
    }

    public void displayPower(String company, boolean power) {
        if(power) {
            System.out.println(company + " TV 전원이 켜졌습니다.");
        } else {
            System.out.println(company + " TV 전원이 종료되었습니다.");
        }
    }

    public void displayChannel(int channel) {
        System.out.println("현재 채널은 " + channel);
    }

    public String getCompany() {
        return company;
    }

    public int getChannel() {
        return channel;
    }

    public boolean isPower() {
        return power;
    }

    public void setChannel(int channel) {
        this.channel = Math.max(channel, 0);
    }

    public void setPower(boolean power) {
        this.power = power;
    }
}
// 멀티 리모컨 인터페이스를 구현하며 TV 추상 클래스를 상속 
public class SamsungTv extends Tv implements MultiRemoteController {
    public SamsungTv(String company) {
        super(company);
    }

    @Override
    public void turnOnOff() {
        setPower(!isPower());
        displayPower(getCompany(), isPower());
    }

    @Override
    public void channelUp() {
        setChannel(getChannel() + 1);
        displayChannel(getChannel());
    }

    @Override
    public void channelDown() {
        setChannel(getChannel() - 1);
        displayChannel(getChannel());
    }
}
// 멀티 리모컨 인터페이스를 구현하며 TV 추상 클래스를 상속
public class LgTv extends Tv implements MultiRemoteController {
    public LgTv(String company) {
        super(company);
    }

    @Override
    public void turnOnOff() {
        setPower(!isPower());
        displayPower(getCompany(), isPower());
    }

    @Override
    public void channelUp() {
        setChannel(getChannel() + 1);
        displayChannel(getChannel());
    }

    @Override
    public void channelDown() {
        setChannel(getChannel() - 1);
        displayChannel(getChannel());
    }
}

public class Main {
    public static void main(String[] args) {
        // LG TV 구현체를 조작
        MultiRemoteController mrc = new LgTv("LG");
        mrc.turnOnOff(); // LG TV 전원이 켜졌습니다.
        mrc.channelDown(); // 0
        mrc.channelUp(); // 1
        mrc.turnOnOff(); // LG TV 전원이 종료되었습니다.

        // 조작 대상을 Samsung TV로 교체
        mrc = new SamsungTv("Samsung");
        mrc.turnOnOff(); // Samsung TV 전원이 켜졌습니다.
        mrc.channelUp(); // 2
        mrc.turnOnOff(); // Samsung TV 전원이 종료되었습니다.

        // 매개변수, 반환타입 다형성 체크
        // 인터페이스 변수 = 구현 객체이기 때문에 자동 타입 변환 발생
        MultiRemoteController samsung = mrc.getTV(new SamsungTv("Samsung"));
        samsung.turnOnOff(); // Samsung TV 전원이 켜졌습니다.
        // 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수이기 때문에 강제 타입 변환 발생 
		SamsungTv samsungTv = (SamsungTv) samsung;
        samsungTv.turnOnOff(); // Samsung TV 전원이 종료되었습니다.

		// 인터페이스 변수 = 구현 객체이기 때문에 자동 타입 변환 발생
		MultiRemoteController lg = mrc.getTV(new LgTv("LG"));
        lg.turnOnOff(); // LG TV 전원이 켜졌습니다.
        // 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수이기 때문에 강제 타입 변환 발생 
		LgTv lgTv = (LgTv) lg;
        lgTv.turnOnOff(); // LG TV 전원이 종료되었습니다.
    }
}

0개의 댓글

관련 채용 정보