본문 바로가기

👩‍💻 BackEnd/☕️ 자바 [Java]

[Java] 자바의 정석 : 인터페이스와 다형성

인터페이스를 이용한 다형성

인터페이스도 구현 클래스의 부모? yes 

인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체만 가능 ( 외울 정도로 반복하기 ) 
interface Fightable {
	void move(int x, int y); 
	void attack(Fightable f); // Fightable 을 구현한 클래스의 인스턴스만 가능 
}

 

다중 상속의 문제 

  • 충돌 발생 : 같은 메서드가 있을 경우 어떤 것을 물려받을지 충돌
  • 인터페이스는 구현부가 없기 때문에 충돌 안남. 

 

인터페이스를 메서드의 리턴타입으로 지정할 수 있다. 
// Fightable 인터페이스를 구현한 클래스를 반환한다는 의미 
Fightable method(){
	Fighter f = new Fighter(); 
	return f;   
    
	// return new Fighter(); 한 문장으로 줄이면 다음과 같다. 
}

class Fighter extends Unit implement Fightable{
	public void move(int x, int y){/* 생략 */};
	public void attack(Fightable f){/* 생략 */}; 
}
  • 다형성 때문에 fighter 객체 반환 가능 
  • 메서드의 반환 타입이 인터페이스면 이 인터페이스를 구현한 객체를 리턴해야한다. 

 

[정리]

  • 메서드의 반환 타입이 인터페이스일 경우, 반환타입은 인터페이스를 구현한 객체를 반환해야한다. 
  • 메서드를 호출한 쪽에서는 반환타입과 일치하거나 자동형변환이 가능한 타입의 변수에 결과를 저장해야한다. 

 

예제 

 

인터페이스와 추상 클래스 선언 
interface Fightable1 {
    void move(int x, int y);

    void attack(Fightable1 f);

}

abstract class Unit2 {
    int x, y;

    abstract void move(int x, int y);

    void stop() {
        System.out.println("멈춥니다.");
    }
}
  • Fightable1은 move, attack 메서드를 갖고
  • Unit2 는 move, stop 메서드를 갖는다. 

 

 

Fighter 클래스 생성 후 Unit2 를 상속받고, Fightable1 을 구현한다. 
class Fighter extends Unit2 implements Fightable1 {

    @Override
    public void move(int x, int y) {
        System.out.println("[ " + x + ", " + y + " ] 로 이동");
    }

    @Override
    public void attack(Fightable1 f) {
        System.out.println(f.toString() + "를 공격합니다. ");
    }
}
  • move, attack 메서드를 오버라이딩한다. 
  • move 메서드는 추상 클래스인 Unit 애도 있고, 인터페이스인 Fightable 에도 존재하는 메서드이지만 인터페이스의 둘 다 구현부가 없고 선언부만 있는 추상 메서드였기 때문에 충돌이 일어나지 않고 오버라이딩이 되는 걸 발견할 수 있다. (인터페이스의 다중 상속이 가능한 이유) 
  • 오버라이딩 조건 : 조상의 접근 제어 범위보다 좁으면 안된다. 
  • 인터페이스의 추상 메서드의 접근 제어자는 모두 public 이기 때문에 오버라이딩 한 메서드의 접근 제어자도 public 이어야 한다. 

 

인스턴스 생성 후 호출해보기 
public class FighterTest {
    public static void main(String[] args) {

        Fighter f = new Fighter();
        f.move(100, 200);
        f.attack(f);

        Fightable1 f2 = new Fighter();
        f2.move(1000, 2000);    // Fightable1 인터페이스에는 stop() 메서드가 없기 때문에 호출 불가능 
        f2.attack(f2);

        Unit2 u = new Fighter();
        u.move(100,300);
        u.stop(); // Unit 클래스에는 attack 메서드가 없기때문에 호출 불가능
    }
}

 

이렇게 main 메서드에서 각각의 인스턴스 생성 후 호출을 해보면 다음과 같다.

  • Fighter 클래스의 인스턴스 생성 후 
  • Fightable1 인터페이스를 참조변수 타입으로 설정 후 인스턴스 생성
    • 해당 인터페이스에만 존재하는 메서드 호출 가능 
  • Unit2 추상 클래스를 참조 변수 타입으로 설정 후 인스턴스 생성 
    • 해당 추상 클래스에만 존재하는 메서드 호출 가능 
  • 즉, 참조변수를 생성 할 때 참조변수 타입이 리모콘 기능을 한다고 생각하면 편함. 
    • Unit2 의 리모콘으로 객체를 생성하면 당연히 Unit2 에만 있는 기능을 사용할 수 있다고 생각하자. 

인터페이스의 다중 구현을 허용한다.