9osari.log
← 목록으로

[JAVA] 다형성_2

2025년 4월 26일 · #java#김영한-java-기본

다형성의 활용

다형성을 왜? 사용하는지

다형성을 사용하지 않고 프로그램을 만든 후 다형성을 사용해보자

개, 고양이, 소의 울음 소리를 테스트 하는 프로그램 작성

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

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

public class Dog {
    public void sound() {
        System.out.println("멍멍멍");
    }
}
public static void main(String[] args) {
    Dog dog = new Dog();
    Cat cat = new Cat();
    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("동물 소리 테스트 종료");
}

중복제거

메서드 사용

메서드를 사용해 중복을 제거하려면 매개변의 클래스를 Dog, Cat, Caw 중 하나로 정해야한다.

private static void soundCaw(Caw caw) {
	System.out.println("동물 소리 테스트 시작");
	caw.sound();
	System.out.println("동물 소리 테스트 종료");
}

배열, for문 사용

Caw[] cawArr = {cat, dog, caw}; //컴파일 오류 발생!
System.out.println("동물 소리 테스트 시작");
	for (Caw caw : cawArr) {
		cawArr.sound();
	}
System.out.println("동물 소리 테스트 종료");

Dog, Cat, Caw 는 서로 타입(클래스)가 다르기 때문배열에 담거나 메서드 매개변수로 함께 전달할 수 없다.

다형성 사용

부모 클래스

package poly.ex2;

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

자식 클래스

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

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

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

메인 클래스

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

    }

    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}
public static void main(String[] args) {
    Animal[] animalArr = {new Dog(), new Cat(), new Cow(), new Duck(), new Pig()};

    for (Animal animal : animalArr) {
        soundAnimal(animal);
    }
}

image

하지만 문제점이 있다.

  1. Animal을 직접 생성할 수 있다.
Animal animal = new Animal(); // 이상하지만 가능함
  1. sound() 오버라이딩을 누락할 수도 있다.
public class Pig extends Animal {
    // sound() 오버라이딩 안 함
}

그래서 자바에서 제공하는 추상 클래스추상 메서드를 사용하면 된다.

추상 클래스

추상 클래스를 사용하면 다형성을 더 안전하게 사용할 수 있다.

동물( Animal )과 같이 부모 클래스는 제공하지만, 실제 생성되면 안되는 클래스를 추상 클래스라 한다.

따라서

public abstract class AbstractAnimal {

    public abstract void sound();
    public abstract void eat();

    public void sleep() {
        System.out.println("Sleep");
    }
}

자식 클래스

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

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

    @Override
    public void eat() {
        System.out.println("Cow.eat");
    }
}
public class Cat extends AbstractAnimal {
    @Override
    public void sound() {
        System.out.println("Cat.sound");
    }

    @Override
    public void eat() {
        System.out.println("Cat.eat");
    }

    @Override
    public void sleep() {
        System.out.println("Cat.sleep");
    }
}

메인 클래스

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

        soundAnimal(cat);
        soundAnimal(cow);
        soundAnimal(dog);
        eatAnimal(cat);
        eatAnimal(cow);
        eatAnimal(dog);
        cat.sleep();
        cow.sleep();
        dog.sleep();
    }
    
    private static void soundAnimal(AbstractAnimal animal) {
        animal.sound();
    }

    public static void eatAnimal(AbstractAnimal animal) {
        animal.eat();
    }
}

//출력결과
Cat.sound
Cow.sound
Dog.sound
Cat.eat
Cow.eat
Dog.eat
Cat.sleep
Sleep
Sleep

순수 추상 클래스

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

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

순수 추상 클래스의 개념은 프로그래밍에서 매우 자주 사용된다. 자바는 순수 추상 클래스를 더 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공한다.

인터페이스

왜 인터페이스가 필요한지?

인터페이스란?

public interface InterfaceAnimal {
    public void sleep();
}

자식 클래스

public class Cat implements InterfaceAnimal {
    @Override
    public void sleep() {
        System.out.println("cat sleep");
    }
}

public class Dog implements InterfaceAnimal {
    @Override
    public void sleep() {
        System.out.println("dog sleep");
    }
}

public class Cow implements InterfaceAnimal{
    @Override
    public void sleep() {
        System.out.println("cow sleep");
    }
}

메인 클래스

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

        Cat cat = new Cat();
        Dog dog = new Dog();
        Cow cow = new Cow();

        sleep(cat);
        sleep(dog);
        sleep(cow);
    }

    private static void sleep(InterfaceAnimal animal) {
        System.out.println("---sleep Start");
        animal.sleep();
        System.out.println("sleep End---");
    }
}

//출력결과
---sleep Start
cat sleep
sleep End---
---sleep Start
dog sleep
sleep End---
---sleep Start
cow sleep
sleep End---

순수 추상 클래스 예제와 거의 유사하다. 순수 추상 클래스가 인터페이스가 되었을 뿐이다.

메서드 규약 = “설계도”

인터페이스는 메서드 이름만 존재하는 설계도이다.

실제 로직은 모두 자식 클래스가 구현한다.

이 구조 덕분에 인터페이스를 기준으로 코드를 작성하면 실제 구현체가 무엇이든 관계없이 유연하게 동작한다.

다형성과 인터페이스

private static void sleep(InterfaceAnimal animal) {
        System.out.println("---sleep Start");
        animal.sleep();
        System.out.println("sleep End---");
    }

인터페이스의 장점

좋은 프로그램은 제약이 있는 프로그램 이다.

인터페이스는 자바에서 가장 순수한 형태의 제약(규약)을 제공한다.

규약을 기반으로 다형성유연함을 갖춘 확장 가능한 프로그램을 만들 수 있다.

인터페이스 다중 구현

자바는 다중 상속을 지원하지 않음!

그래서 자바는 클래스의 다중 상속은 금지하고

대신 인터페이스의 다중 구현만 허용

public interface Wifi {
    void scanWifi();
    void sendData();
}

public interface Bluetooth {
    void scanBluetooth();
    void sendData();  //wifi에도 같은 이름의 메서드 있
}

public class Phone implements Wifi, Bluetooth {
    @Override
    public void scanWifi() {
        System.out.println("Phone.scanWifi");
    }

    @Override
    public void scanBluetooth() {
        System.out.println("Phone.scanBluetooth");
    }

    @Override
    public void sendData() {
        System.out.println("Phone.sendData");
    }
}

메인 클래스

public class PhoneMain {
    public static void main(String[] args) {
        Wifi wifi = new Phone();
        wifi.scanWifi();
        wifi.sendData();

        Bluetooth bluetooth = new Phone();
        bluetooth.scanBluetooth();
        bluetooth.sendData();
    }
}

//출력결과
Phone scanWifi
Phone sendData
Phone scanBluetooth
Phone sendData

어떤 타입으로 참조하든, 결국 자식 클래스의 오버라이딩 메서드가 실행됨

이 구조가 인터페이스 다중 구현의 핵심이며, 자바가 클래스 다중 상속은 막고 인터페이스만 허용하는 이유이다. 이런 구조가 유연한 확장성과 유지보수의 핵심!

클래스와 인터페이스 활용

자동자 제어 프로그램을 만들어보자

추상 클래스

public abstract class Car {
    //abstract 추상 클래스
    public abstract void startEngine();
    public abstract void stopEngine();

    public void hook() {
        System.out.println("Car hook");
    }
}

인터페이스

public interface Electric {
    void charge();
}

public interface AutoDrive {
    void autoDrive();
}

클래스 - 아반떼

public class Avante extends Car {
    @Override
    public void startEngine() {
        System.out.println("Avante Engine Start");
    }

    @Override
    public void stopEngine() {
        System.out.println("Avante Engine Stop");
    }
}

클래스 - 테슬라

public class Tesla extends Car implements Electric, AutoDrive {
    @Override
    public void startEngine() {
        System.out.println("Tesla Engine Start");
    }

    @Override
    public void stopEngine() {
        System.out.println("Tesla Engine Stop");
    }

    @Override
    public void charge() {
        System.out.println("Tesla 충전중...");
    }

    @Override
    public void autoDrive() {
        System.out.println("Tesla Auto Drive Start");
    }
}

메인

public class CarMain {
    public static void main(String[] args) {
        Car avante = new Avante();
        drive(avante);

        Car tesla = new Tesla();
        drive(tesla);

        Electric eCar = new Tesla();
        eCar.charge();

        AutoDrive autoCar = new Tesla();
        autoCar.autoDrive();
    }

    private static void drive(Car car) {
        car.startEngine();
        car.hook();
        car.stopEngine();
    }
}

//출력
Avante Engine Start
Car hook
Avante Engine Stop

Tesla Engine Start
Car hook
Tesla Engine Stop

Tesla 충전중...
Tesla Auto Drive Start

이처럼 클래스 상속인터페이스 구현을 함께 사용하면, 객체지향 설계를 더 유연하고 확장 가능하게 만들 수 있다.

실생활의 복합 기능 구조를 코드에 자연스럽게 표현할 수 있어 실무에서도 자주 사용되는 패턴이다.


출처: [김영한의 실전 자바 - 기본편](https://www.inflearn.com/course/김영한의-실전-자바-기본편/dashboard)
···
← PREV [JAVA] 다형성_1 NEXT → [JAVA Questions] 접근제어자 복습