코드 저장소.

[이것이 자바다] Chapter8장 인터페이스 본문

Java

[이것이 자바다] Chapter8장 인터페이스

slown 2023. 2. 17. 22:39

신용권 님의 ''이것이 자바다'' 8장 공부 기록

8.인터페이스

8.1. 인터페이스란?

인터페이스 는 객체의  사용방법을 정의하는 타입을 말한다.

- 인터페이스는 다형성을 구현하는 매우 중요한 역할을 한다.

 

8.2. 인터페이스 선언

인터페이스는 선언은 class 대신에 interface키워드를 사용한다.

[public] interface 인터페이스명{
 //상수
 타입 상수명 = 값;
 //추상 메서드
 타입 메소드명(매개변수..);
 //디폴트 메서드 
 default 타입 메서드(매개변수,....){...}
 //정적 메서드
 static 타입 메소드명(매개변수){...}
  • 상수 필드
    • 인터페이스는 런타입 시 데이터를 저장할 수 있는 필드를 선언을 할 수 없다.
    • 상수는 이터페이스에 고정된 값으로 런타임 시에 데이터를 바꿀 수 없다. 
    • 상수를 선언할 때에는 반드시 초기값을 대입해야 한다.
[public static final] 타입 상수명 = 값;
  •  
  • 추상 메서드
    • 객체가 가지고 있는 메소드를 설명한 메소드이다.
    • 호출할 때 어떤 매개값이 필요하고,리턴 타입이 무엇인지만 알려준다.
    • 구체적인 내용은 구현객체가 각지고 있다.
[public abstract] 리턴타입 메소드명(매개변수....);
  • 디폴트 메서드
    • 구현 객체가 가지고 있는 인스턴스 메소드이다.
    • 기존 인터페이스를 확장해서 새로운 기능을 호출이 가능하다.
[public] default  리턴타입 메소드명(매개변수,...){...}
  • 정적 메서드
    • 정적 메서드는 객체가 없어도 인터페이스만으로 호출이 가능하다.
[public] static 리턴타입 메소드명(매개변수,...){...}

8.3.인터페이스 구현

8.3.1. 구현 클래스

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

구현 클래스에서 인터페이스의 추상 메소드에 대한 실체 메소드를 구현할 때 주의할 점은 인터페이스의 모든 메소드는 기본적으로 public 접근 제한을 갖기 떄문에 public 보다 낮은 접근 제한으로 작성할 수 없다. 그리고 public을 생략한 경우에는 "Cannot reduce the visibility of the inherited method(상속된 메소드의 가시성을 줄일 수 없다)" 컴파일 에러가 발생한다.

 

이런 경우에는 인터페이스에 선언된 추상 메소드에 대응하는 실체 메소드를 구현 클래스가 작성하지 않으면 구현 클래스는 자동적으로 추상 클래스가 된다.

public abstract class Television implements RemoteControl{
	//실체 메소드를 작성하지 않음
    public void turnOn(){}
    public void turnOff(){}
}

- 구현 클래스의 실체 메소드에 @Override 어노테이션을 붙이면 컴파일러가 실체 메소드인지를 체크한다.

 

- 구현 클래스가 작성되면 new 연산자로 객체를 생성하여 인터페이스에 대입할 수 있다. 인터페이스 변수는 참조 타입이므

  로 구현객체의 번지를 저장한다.

 

8.3.2.익명 클래스 구현

인터페이스 변수 = new 인터페이스{
//인터페이스에 선언된 추상 메소드의 실제 메소드 선언
}

-중괄호에는 인터페이스에 선언된 모든 추상 메소드들의 실체 메소드를 작성해야 한다. 그렇지 않으면 컴파일 에러가 발생한다.

 

-추가적으로 필드와 메소드 선언이 가능하나, 익명 객체 안에서만 사용할 수 있고 인터페이스 변수로 접근할 수 없다.

 

-모든 객체는 클래스로부터 생성된다. 익명 구현 객체의 소스코드를 컴파일하면 컴파일러에 의해 "클래스명$1.class"의 이름으로 파일이 생성된다.

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

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

객체는 다수의 인터페이스 타입으로 사용할 수 있다.

8.4.타입 변환과 다형성

클래스에 문제가 있어 다른 클래스를 만들 때는 같은 메소드를 사용한다면 메소드 선언부가 동일해야 한다. 인터페이스를 추상 메소드를 작성하고 구현 클래스로 해당 메소드를 작성할 시에 이러한 문제를 해결할 수 있다. 처음부터 메소드 선언부가 동일하기 때문이다.

 

인터페이스는 메소드의 매개 변수로 많이 등장하는데, 해당 매개 값으로 여러 종류의 구현 객체를 줄 수 있으므로 메소드 실행 결과가 다양하게 나온다. 이것이 인터페이스 매개 변수의 다형성이다.

8.4.1.자동 타입 변환(Promotion)

인터페이스 변수(자동 타입 변환) = 구현 객체;

 

구현 클래스를 상속하여 자식 클래스를 만들었다면 자식 객체 역시 인터페이스 타입으로 자동 타입 변환시킬 수 있다. 이를 통해 필드의 다형성과 매개 변수의 다형성을 구현할 수 있다.

 

8.4.2.필드의 다형성

같은 인터페이스의 구현 객체는 교체될 수 있다. 따라서 개발 코드의 수정 없이도 다양한 결과(개발 코드에서의 인터페이스 메소드 실행 등)를 얻을 수 있다.

8.4.3.인터페이스 배열로 구현 객체 관리

인터페이스 배열을 통해 여러 구현 객체들을 인덱스로 표현하여 관리할 수 있다. 이렇게 하면 제어문에서 가장 큰 이점이 있다.

Tire[] tires ={
    new HTire();
    new HTire();
    new HTire();
    new HTire();
}
void run(){
    for(Tire tire : tires){
        tire.roll();
    }
}

8.4.4 매개 변수의 다형성

  • 자동 타입 변환은 주로 메소드를 호출할 때 많이 발생한다. 매개 변수를 인터페이스 타입으로 선언하고 호출 시 구현 객체를 대입한다.
//Driver 클래스
public class Driver{
    public void drice(Vehicle vehicle){
        vehicle.run();
    }
}
//Vehicle 인터페이스 타입
public interface Vehicle{
    public void run();
}
//구현 객체 Bus
public class Bus implements Vehicle{
    @Override
    public void run(){
        System.out.println("버스가 달립니다");
    }
}
//매개 변수의 다형성 테스트
public class DriverEx{
    public static void main(String[] args){
        Driver driver = new Driver()
            
        Bus bus = new Bus();
        
        driver.drive(bus);//bus ->자동 타입 변환됨
    }
}
  • 인터페이스가 매개변수 타입으로 제공될 경우, 어떠한 구현 객체도 매개값으로 사용할 수 있고, 이를 통해 메소드의 실행결과가 다양해질 수 있게 된다.(매개 변수의 다형성)

8.4.5 강제 타입 변환

  • 구현 객체가 인터페이스 타입으로 자동 변화하면, 인터페이스에 선언된 메소드만 사용이 가능하다. 그러나 경우에 따라 구현 클래스에 선언된 필드와 메소드를 사용해야 할 수 있는데, 이 때 강제 타입 변환을 사용하여 구현 클래스의 필드와 메소드를 사용할 수 있다.
구현 클래스 변수 = (구현 클래스) 인터페이스 변수;//강제 타입 변환

8.4.6 객체 타입 확인

  • 강제 타입변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능하다. 이 때 상속에서 객체 타입 확인을 한 것처럼, 인터페이스 타입도 객체의 타입 확인이 필요하다. 어떤 구현 객체가 변환되어 있는지 알지 못하고 변환할 경우 ClassCastException이 발생할 수 있기 때문이다.
  • 객체 타입 확인을 위하여 instanceof 연산자를 사용할 수 있다.
if(vehicle instanceof Bus){
    Bus bus = (Bus) vehicle; //안심하고 변환 가능
}

 

8.5 인터페이스 상속

인터페이스도 다른 인터페이스를 상속할 수 있다. 인터페이스는 클래스와 달리 다중 상속을 허용한다.

public interface 하위인터페이스 extends 상위인터페이스1, 하위인터페이스2 {...}

 

하위 인터페이스의 구현 클래스는 하위, 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 한다. 따라서 해당 구현 클래스로부터 객체를 생성하고 나서 하위 및 상위 인터페이스 타입으로 변환이 가능하다.

하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);

 

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

8.6 디폴트 메소드와 인터페이스 확장

디폴트 메소드는 인터페이스에서 선언된 인스턴스 메소드이므로 구현 객체가 있어야 사용가능하다. 왜 선언은 인터페이스에서 하고, 사용은 구현 객체를 통해 하는 것일까?

8.6.1 디폴트 메소드의 필요성

   인터페이스에서 디폴트 메소드가 필요한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위해서이다. 

  • 추상 클래스는 구현 클래스에서 실행 내용을 채워야 하지만, 디폴트 메소드는 인터페이스에 정의된 것을 그냥 사용해도 되고, 필요에 따라 재정의해서 사용할 수도 있다.

8.6.2 디폴트 메소드가 있는 인터페이스 상속

  • 부모 인터페이스에 디폴트 메소드가 정의되어 있을 경우, 자식 인터페이스에서 디폴트 메소드를 활용하는 방법은 다음 세 가지가 있다.
  1. 디폴트 메소드를 단순히 상속만 받는다.
//부모 인터페이스
public interface ParentInterface{
    public void method1();
    public default void method2(){/*실행문*/}
}
//자식 인터페이스1
public interface ChildInterface1 extends ParentInterface {
    public void method3;
}
//자식 인터페이스1을 구현하는 클래스는 method1()과 method3()의 실체 메소드를 가지고 있어야 하며 method2()를 호출할 수 있다.
ChildInterface1 ci1 = new ChildInterface1(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci1.method1();
ci1.method2();//ParentInterface의 method2() 호출
  1. 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경한다.
//자식 인터페이스2
public interface ChildInterface2 extends ParentInterface {
    @Override
    public default void method2() {/*실행문*/} //재정의
    public void method3();
}
//자식 인터페이스1 구현 클래스와 유사함.
ChildInterface2 ci2 = new ChildInterface2(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci2.method1();
ci2.method2();//ParentInterface의 method2() 호출
  1. 디폴트 메소드를 추상 메소드로 재선언한다.
//자식 인터페이스3
public interface ChildInterface3 extends ParentInterface {
    @Override
    public void method2() //추상 메소드로 재선언
    public void method3();
}
//자식 인터페이스3을 구현하는 클래스는 method1(), method2(), method3()의 실체 메소드를 모두 가지고 있어야 한다.
ChildInterface2 ci3 = new ChildInterface3(){ //익명 구현 객체
    @Override
    public void method1() {/*실행문*/}
    @Override
    public void method2() {/*실행문*/}
    @Override
    public void method3() {/*실행문*/}
};

ci3.method1();
ci3.method2();//ChildInterface3 구현 객체의 method2() 호출