본문 바로가기
IT/김영한의실전JAVA-기본_inFlearn

11. 다형성2 - 다형성 활용 1~3 / 추상 클래스 1~2 / 인터페이스 / 인터페이스(다중 구현) / 클래스와 인터페이스 활용

by for-learn 2025. 4. 14.

11-1. 다형성 활용1

다형성을 왜 사용하는지 장점을 알아보기 위해 

다형성을 사용하지 않는 프로그램 개발과

이후, 다형성을 사용한 코드 변경

(동물 소리 문제 - 다형성 사용 안함)

poly.ex1.Dog.java

package poly.ex1;

public class Dog {
    public void sound() { System.out.println("멍멍"); }
}

poly.ex1.Cat.java

package poly.ex1;

public class Cat {
    public void sound() { System.out.println("액옹"); }
}

poly.ex1.Cow.java

package poly.ex1;

public class Cow {
    public void sound() { System.out.println("음머"); }
}

poly.ex1.AnimalSoundMain.java

package poly.ex1;

public class AnimalSoundMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat(); // ctrl+alt+v 단축키(Introduce Variable)
        Cow cow = new Cow();

        System.out.println("동물 소리 테스트 시작");
        dog.sound();
        System.out.println("동물 소리 테스트 종료");

        System.out.println("동물 소리 테스트 시작");
        cat.sound();
        System.out.println("동물 소리 테스트 종료");

        System.out.println("동물 소리 테스트 시작");
        cow.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}
  • 새로운 동물이 추가된다고 가정했을 때, 출력 코드가 중복된다. 
  • 중복 제거를 위해서는 메서드 사용, 배열&for문을 사용하면 된다.
  • 하지만 Dog, Cat, Cow는 서로 완전히 다른 클래스

코드 중복 제거 시도

메서드로 중복 제거 시도

...
    // 메서드로 중복 제거 시도 => 메서드가 많아져 실패
    soundCat(cat);
    soundCow(cow);
}
private static void soundCat(Cat cat) {
    System.out.println("동물 소리 테스트 시작");
    cat.sound();
    System.out.println("동물 소리 테스트 종료");
}

private static void soundCow(Cow cow) {
    System.out.println("동물 소리 테스트 시작");
    cow.sound();
    System.out.println("동물 소리 테스트 종료");
}
  • 매개변수의 클래스를 Cow, Dog, Cat  중 하나로 정해야 한다. 
  • 매개변수를 Cow로 했을 때, 해당 메서드는 Cow의 전용 메서드가 된다.
  • Dog, Cat, Cow의 타입(클래스)이 달라 soundCow메서드를 함께 사용하는 것이 불가능 하다.  

배열과 for문으로 중복 제거 시도

...
// 배열과 for문으로 중복 제거 시도 => 서로 다른 타입의 클래스를 배열에 담는 것은 불가능
Cow[] cowArr = {cat, dog, cow}; // 컴파일 오류
...
  • 배열 타입을 Dog, Cat, Cow 중 하나로 지정해야 함
  • 타입이 서로 다른 Dog, Cat, Cow를 하나의 배열에 담을 수 없음

Dog, Cat, Cow가 모두 같은 타입을 사용할 수 있다면 코드 중복 제거 가능

 

11-2. 다형성 활용2

다형성을 사용하기 위해 상속 관계 사용

Animal 이라는 부모 클래스를 만들고 메서드를 자식 클래스에서 오버라이딩하게 함

(동물 소리 문제 - 다형성 사용해 변경)

poly.ex2.Animal.java (부모 클래스)

package poly.ex2;

public class Animal {
    public void sound() {
        System.out.println("동물 울음 소리");
    }
}

poly.ex2.Dog.java

package poly.ex2;

public class Dog extends Animal{
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

poly.ex2.Cat.java

package poly.ex2;

public class Cat extends Animal{
    @Override
    public void sound() {
        System.out.println("에옭");
    }
}

poly.ex2.Cow.java

package poly.ex2;

public class Cow extends Animal{
    @Override
    public void sound() {
        System.out.println("음머");
    }
}

poly.ex2.AnimalPolyMain1.java

package poly.ex2;

public class AnimalPolyMain1 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Cow cow = new Cow();

        soundAnimal(dog); // 부모는 자식을 담을 수 있다.
        soundAnimal(cat);
        soundAnimal(cow);
    }
    
    // 매개변수 'Animal animal' 이 코드의 핵심
    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

매개변수 Animal animal

private static void soundAnimal(Animal animal)

  • 다형적 참조 덕분에 animal 변수는 자식인 Dog, Cat, Cow의 인스턴스를 참조할 수 있다. 
  • 메서드 오버라이딩 덕분에 animal.sound()를 호출해도,
    Dog.sound(), Cat.sound() 같이 각 인스턴스의 메서드 호출 가능
  • 다형성 덕분에 이후, 새로운 동물을 추가해도 soundAnimal() 메서드 코드를 그대로 재사용할 수 있다. 
package poly.ex2;

public class Duck extends Animal{
    @Override
    public void sound() { System.out.println("꽉꽉"); }
}

 

11-3. 다형성 활용3

배뎔과 for문을 사용해 중복 제거

poly.ex2.AnimalPolyMain2.java

package poly.ex2;

public class AnimalPolyMain2 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Cow cow = new Cow();
        Duck duck = new Duck();
        Animal[] animalArr = {dog, cat, cow, duck};

        // 변하지 않는 부분
        for (Animal animal : animalArr) {
            System.out.println("동물 소리 테스트 시작");
            animal.sound();
            System.out.println("동물 소리 테스트 종료");
        }
    }
}

  • 배열은 같은 타입의 데이터를 나열할 수 있다. 

poly.ex2.AnimalPolyMain3.java (조금 더 개선)

package poly.ex2;

public class AnimalPolyMain3 {
    public static void main(String[] args) {
        // ctrl+alt+n 단축키(Inline Variable)
        Animal[] animalArr = {new Dog(), new Cat(), new Cow()};
        for (Animal animal : animalArr) {
            soundAnimal(animal);
        }
    }

    // 코드를 메서드로 만드는 단축키 => ctrl+alt+m
    // 변하지 않는 부분
    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}
  • 새로운 동물이 추가되었을 때, 코드가 변하는 부분과 변하지 않는 부분이 구분된다. 
  • 새로운 기능이 추가되었을 때, 변하는 부분을 최소화하는 것이 잘 작성된 코드이다. 

남은 2가지 문제

1. Animal 클래스를 생성할 수 있는 문제

  • Animal animal = new Animal();
  • 위와 같이 동물이라는 추상적인 개념이 실제로 존재하는 것은 이상하다.
  • 다형성을 위해 필요하지, 직접 인스턴스를 생성해서 사용할 일은 없다. 
  • 하지만 Animal도 클래스이기 때문에 인스턴스를 생성하고 사용하는데 아무런 제약이 없다.
  • 누군가 new Animal()로 Animal 인스턴스를 생성할 수 있다. 

2. Animal 클래스를 상속 받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 가능성

  • Animal을 상속 받은 Pig 클래스를 만든다고 했을 때,
  • 개발자가 실수로 sound() 메서드를 오버라이딩하지 않았다. 
  • 코드상 아무 문제도 없지만,
    프로그램을 실행하면 "꾸에엑"이 아닌 부모 클래스의 Animal.sound()가 호출될 것이다.

이를 해결하려면, 추상 클래스와 추상 메서드를 사용하면 된다.

 

11-4. 추상 클래스1

추상 클래스

  • 동물(Animal)과 같이 부모 클래스는 제공하지만, 실제 생성(인스턴스)되면 안되는 클래스
  • 추상적인 개념을 제공하는 클래스로, 실체인 인스턴스가 존재하지 않음
  • 상속을 목적으로 사용되고 부모 클래스 역할을 담당
  • public abstractclass AbstractAnimal {...}
  • 추상 클래스 선언 시 앞에 추상이라는 의미의 abstract 키워드를 입력
  • 기존 클래스와 같지만, 직접 인스턴스를 생성하지 못하는 제약이 추가
    new AbstractAnimal() => X

추상 메서드

  • 부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야하는 메서드
  • 추상적인 개념을 제공하는 메서드로, 실체가 존재하지 않고 바디가 없음
  • public abstract void sound();
  • 추상 메서드 선언시 메서드 앞에 abstract 키워드 입력
  • 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 함
    • 그렇지 않으면 컴파일 오류 발생
    • 추상 메서드는 메서드 바디가 없음
    • 작동하지 않는 메서드를 가진 불완전한 클래스로 볼 수 있다. 
    • 직접 생성하지 못하도록 추상 클래스로 선언
  • 추상 메서드는 상속 받는 자식 클래스가 반드시 오버라이딩해서 사용
    • 그렇지 않으면 컴파일 오류 발생
    • 추상 메서드는 자식클래스가 반드시 오버라이딩 해야 해서 메서드 바디 부분이 없음
    • 메서드 바디가 있으면 컴파일 오류 발생
    • 오버라이딩 하지 않으면 자식 클래스도 추상 클래스여야 함.
  • 추상 메서드는 기존 메서드와 같지만,
    메서드 바디가 없고,
    자식 클래스가 해당 메서드를 반드시 오버라이딩 해야 하는 제약이 추가됨

(동물 소리 문제 - 추상 클래스/메서드 사용해 변경)

poly.ex3.AbstractAnimal.java (추상)

package poly.ex3;

public abstract class AbstractAnimal {
    public abstract void sound();

    public void move() {
        System.out.println("동물이 움직입니다.");
    }
}
  • AbstractAnimal은 추상 클래스로 직접 인스턴스를 생성할 수 없다.
  • sound()는 추상 메서드로 자식이 반드시 오버라이딩 해야 한다. 
  • move()는 추상 메서드가 아니므로 자식 클래스가 오버라이딩 하지 않아도 된다. 

poly.ex3.Dog.java

package poly.ex3;

public class Dog extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("아르르르"); }
}

poly.ex3.Cat.java

package poly.ex3;

public class Cat extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("미야옹"); }
}

poly.ex3.Cow.java

package poly.ex3;

public class Cow extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("음머어"); }
}

poly.ex3.AbstractMain.java 

package poly.ex3;

public class AbstractMain {
    public static void main(String[] args) {
        // 추상클래스 생성 불가
        //AbstractAnimal animal = new AbstractAnimal();

        Dog dog = new Dog();
        Cat cat = new Cat();
        Cow cow = new Cow();
        
        cat.sound();
        cat.move();
        
        soundAnimal(cat);
        soundAnimal(dog);
        soundAnimal(cow);
    }
    
    // 동물이 추가 되어도 변하지 않는 코드
    private static void soundAnimal(AbstractAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

 

추상 클래스로 실수로 Animal 인스턴스를 생성할 문제를 근본적으로 방지

추상 메서드로 자식 클래스가 sound() 메서드를 오버라이딩 하지 않을 문제를 근본적으로 방지

 

11-5. 추상 클래스2

순수 추상 클래스: 모든 메서드가 추상 메서드인 추상 클래스

(동물 소리 문제 - 순수 추상 클래스 사용해 변경)

poly.ex4.AbstractAnimal.java (순수 추상)

package poly.ex4;

public abstract class AbstractAnimal {
    public abstract void sound();
    public abstract void move();
}

poly.ex4.Dog.java

package poly.ex4;

public class Dog extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("아르르르"); }

    @Override
    public void move() { System.out.println("개 이동"); }
}

poly.ex4.Cat.java

package poly.ex4;

public class Cat extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("미야옹"); }

    @Override
    public void move() { System.out.println("고양이 이동"); }
}

poly.ex4.Cow.java

package poly.ex4;

public class Cow extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("음머어"); }

    @Override
    public void move() { System.out.println("소 이동"); }
}

poly.ex4.AbstractMain.java

package poly.ex4;

public class AbstractMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Cow cow = new Cow();

        soundAnimal(cat);
        soundAnimal(dog);
        soundAnimal(cow);

        moveAnimal(cat);
        moveAnimal(dog);
        moveAnimal(cow);
    }

    // 변하지 않는 코드
    private static void soundAnimal(AbstractAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }

    // 변하지 않는 코드
    private static void moveAnimal(AbstractAnimal animal) {
        System.out.println("동물 이동 테스트 시작");
        animal.move();
        System.out.println("동물 이동 테스트 종료");
    }
}

 

순수 추상 클래스

  • 모든 메서드가 추상 메서드여서 코드를 실행할 바디 부분이 전혀 없다. 
  • 실행 로직이 없고, 다형성을 위한 부모 타입으로 껍데기 역할만 제공
  • 특징
    • 인스턴스 생성 불가 
    • 상속 시 자식은 모든 메서드를 오버라이딩 해야 한다. 
    • 주로 다형성을 위해 사용
  • 순수 추상 클래스는 마치 어떤 규격을 지켜서 구현해야 하는 것처럼 느껴진다.
    => 일반적으로 이야기하는 인터페이스와 같이 느껴진다.
    • ex) USB 인터페이스는 분명한 규격이 있고 이 규격에 맞추어 제품을 개발해야 연결이 된다.
      순수 추상 클래스가 USB 인터페이스 규격이라고 한다면
      USB 인터페이스에 맞추어 마우스, 키보드 같은 연결 장치들을 구현할 수 있다.
  • 자바는 순수 추상 클래스를 더 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공
    순수 추상 클래스 ≒ 인터페이스

11-6. 인터페이스

순수 추상 클래스

public abstract class AbstractAnimal {
    public abstract void sound();
    public abstract void move();
}

인터페이스

public interface InterfaceAnimal {
    public abstract void sound();
    public abstract void move();
}

인터페이스 - 메서드 public abstract 키워드 생략 가능

public interface InterfaceAnimal {
    void sound();
    void move();
}

 

인터페이스 특징

순수 추상 클래스의 특징 + 약간의 편의 기능

  • 인스턴스 생성 불가
  • 상속 시 모든 메서드를 오버라이딩
  • 주로 다형성을 위해 사용
  • 인터페이스의 메서드는 모두 public, abstract
  • public abstract 생략 가능, 생략 권장
  • 인터페이스는 다중 구현(다중 상속)을 지원

인터페이스와 멤버 변수

public interface InterfaceAnimal {
    public static final double MY_PI = 3.14;
}
  • 인터페이스의 멤버 변수는 public, static, final이 모두 포함되었다고 간주
  • final은 변수의 값을 한번 설정하면 수정할 수 없다는 뜻
  • 자바에서 static final을 사용한 정적이면서 고칠 수 없는 변수를 상수라고 함
  • 관례상 상수는 대문자 언더스코어(_)로 구분
public interface InterfaceAnimal {
    double MY_PI = 3.14;
}
  • public static final 생략이 권장

 

클래스의 상속 관계는 UML에서 실선 사용

인터페이스 구현(상속)관계는 UML에서 점선 사용
=> 상속 받은 메서드를 다 구현해야하기 때문에 '구현'이라고 말함

(동물 소리 문제 - 인터페이스 사용해 변경)

poly.ex5.InterfaceAnimal.java (인터페이스)

package poly.ex5;

public interface InterfaceAnimal {
    void sound(); // public abstract 생략 가능
    void move(); // public abstract 생략 가능
}

poly.ex5.Dog.java

package poly.ex5;

public class Dog implements InterfaceAnimal {

    @Override
    public void sound() { System.out.println("멈머"); }

    @Override
    public void move() { System.out.println("산책"); }
}

poly.ex5.Cat.java

package poly.ex5;

public class Cat implements InterfaceAnimal {

    @Override
    public void sound() { System.out.println("냐옹"); }

    @Override
    public void move() { System.out.println("우다다다"); }
}

poly.ex5.Cow.java

package poly.ex5;

public class Cow implements InterfaceAnimal {

    @Override
    public void sound() { System.out.println("음메"); }

    @Override
    public void move() { System.out.println("우적우적"); }
}

poly.ex5.InterfaceMain.java

package poly.ex5;

public class InterfaceMain {
    public static void main(String[] args) {
        // 인터페이스 생성 불가
        //InterfaceAnimal interfaceMain1 = new InterfaceAnimal();

        Cat cat = new Cat();
        Dog dog = new Dog();
        Cow cow = new Cow();
        soundAnimal(cat);
        soundAnimal(dog);
        soundAnimal(cow);
    }
    // 동물이 추가 되어도 변하지 않는 코드
    private static void soundAnimal(InterfaceAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}

클래스, 추상 클래스, 인터페이스는 모두 똑같다.

  • 프로그램 코드, 메모리 구조상 모두 똑같다.
  • 모두 자바에서는 .class 루어진다.
  • 인터페이스를 작성할 때도 .java 에 인터페이스를 정의
  • 인터페이스는 순수 추상 클래스와 비슷

상속 vs 구현 extends vs implements

  • 부모 클래스의 기능을 자식 클래스가 상속 받을 때 => 클래스는 상속 받는다고 표현
  • 부모 인터페이스의 기능을 자식 클래스가 상속 받을 때 => 인터페이스를 구현한다고 표현
  • 상속은 이름 그대로 부모의 기능을 물려 받는 것이 목적
  • 인터페이스는 모든 메서드가 추상 메서드로 물려받을 수 있는 기능이 없다.
  • 오히려 인터페이스에 정의한 모든 메서드
    자식이 오버라이딩해서 기능을 구현해야 하기에 구현한다고 표현
  • 인터페이스는 메서드 이름만 있는 설계도이고,
    이 설계도가 실제 어떻게 작동하는지는 하위 클래스에서 모두 구현
  • 따라서 인터페이스의 경우 상속이 아니라 해당 인터페이스를 구현한다고 표현
  • 상속과 구현은 사람이 표현하는 단어만 다를 뿐이지 자바 입장에서는 똑같다. 일반 상속 구조와 동일하게 작동

인터페이스를 사용해야 하는 이유

  • 제약
    • 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현하라는 규약(제약)을 주는 것이다.
      ex) USB 인터페이스 규약
    • 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다.
      이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 또 더는 순수 추상 클래스가 아니게 된다.
    • 인터페이스는 모든 메서드가 추상 메서드이기에 이런 문제를 원천 차단할 수 있다.
  • 다중 구현
    • 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다.
    • 인터페이스부모를 여러명 두는 다중 구현(다중 상속)이 가능하다
자바8에 등장한 default  메서드를 사용하면 인터페이스도 메서드를 구현할 수 있다.
하지만 이것은 예외적으로 아주 특별한 경우에만 사용해야 한다.
자바9에서 등장한 인터페이스의 private 메서드도 마찬가지이다.
지금 학습 단계에서는 이 부분들을 고려하지 않는 것이 좋다. 이 부분은 뒤에서 따로 다룬다.

 

11-7. 인터페이스 - 다중구현

자바가 다중 상속을 지원하지 않는 이유

 

  • 비행기와 자동차를 상속 받아 하늘을 나는 자동차를 만든다고 했을 때, 
  • AirplaneCar 입장에서 move()를 호출할 때, 어떤 부모의 기능을 사용해야 할지 결정해야 하는 문제가 발생
    => 다이아몬드 문제
  • 또한, 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해짐
  • 대신에 인터페이스의 다중 구현을 허용해 이런 문제를 피함
  • 인터페이스가 모두 추상 메서드로 이루어져 있기 때문에 다중 구현을 허용함.

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

  • InterfaceA, InterfaceB는 둘다 같은 methodCommon()를 가지고 있고, Child는 두 인터페이스를 구현
  • 인터페이스는 자신이 구현을 가지지 않고 인터페이스를 구현하는 곳에서 해당 기능을 모두 구현해야 함
  • InterfaceA , InterfaceB는 같은 이름의 methodCommon()를 제공하지만, 이것의 기능은 Child가 구현함
  • 오버라이딩에 의해 어차피 Child에 있는 methodCommon()가 호출됨
  • 결과적으로 한 부모의 methodCommon()를 선택하는 것이 아닌, 인터페이스들을 구현한 ChildmethodCommon()를 사용
  • 그래서 인터페이스는 다이아몬드 문제가 발생하지 않음

poly.diamond.InterfaceA.java

package poly.diamond;

public interface InterfaceA {
    void methodA();
    void methodCommon();
}

poly.diamond.InterfaceB.java

package poly.diamond;

public interface InterfaceB {
    void methodB();
    void methodCommon();
}

poly.diamond.Child.java

package poly.diamond;

public class Child implements InterfaceA, InterfaceB {

    @Override
    public void methodA() {
        System.out.println("Child.methodA");
    }

    @Override
    public void methodB() {
        System.out.println("Child.methodB");
    }

    @Override
    public void methodCommon() {
        System.out.println("Child.methodCommon");
    }
}
  • methodCommon()의 경우 양쪽 인터페이스에 다 있지만 같은 메서드이기에 구현은 하나만 하면 됨

poly.diamond.DiamondMain.java

package poly.diamond;

public class DiamondMain {
    public static void main(String[] args) {
        InterfaceA a = new Child();
        a.methodA();
        a.methodCommon();

        InterfaceB b = new Child();
        b.methodB();
        b.methodCommon();
    }
}

 

11-8. 클래스와 인터페이스 활용

클래스 상속과 인터페이스 구현을 함께 사용

  • AbstractAnimal은 추상 클래스
    sound() 는 추상 메서드
    move() 는 추상 메서드가 아닌 상속을 목적으로 사용
  • Fly는 인터페이스
    Bird, Chicken은 날 수 있는 동물이기에 fly() 를 구현해야 함

 

 

 

poly.ex6.AbstractAnimal.java

package poly.ex6;

public abstract class AbstractAnimal {
    public abstract void sound();
    public void move() {
        System.out.println("동물이 이동합니다.");
    }
}

poly.ex6.Fly.java

package poly.ex6;

public interface Fly {
    void fly();
}

poly.ex6.Dog.java

package poly.ex6;

public class Dog extends AbstractAnimal {
    @Override
    public void sound() { System.out.println("멍멍"); }
}

poly.ex6.Bird.java

package poly.ex6;

public class Bird extends AbstractAnimal implements Fly {
    @Override
    public void sound() { System.out.println("짹짹"); }

    @Override
    public void fly() { System.out.println("새 비행"); }
}

poly.ex6.Chicken.java

package poly.ex6;

public class Chicken extends AbstractAnimal implements Fly {
    @Override
    public void sound() { System.out.println("꼭끼오"); }

    @Override
    public void fly() { System.out.println("푸드득"); }
}
  • extends 를 통한 상속은 하나만 할 수 있고
    implements를 통한 인터페이스는 다중 구현 할 수 있기 때문에
    둘이 함께 나온 경우 extends가 먼저 나와야 한다.

poly.ex6.SoundFlyMain.java

package poly.ex6;

public class SoundFlyMain {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Bird bird = new Bird();
        Chicken chicken = new Chicken();
        
        soundAnimal(dog);
        soundAnimal(bird);
        soundAnimal(chicken);
        
        flyAnimal(bird);
        flyAnimal(chicken);
    }
    
    // AbstractAnimal 사용 가능
    private static void soundAnimal(AbstractAnimal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
    
    // Fly 인터페이스가 있으면 사용 가능
    private static void flyAnimal(Fly fly) {
        System.out.println("날기 테스트 시작");
        fly.fly();
        System.out.println("날기 테스트 종료");
    }
}