Post

[JAVA] 다형성_2

[JAVA] 다형성_2

다형성의 활용

다형성을 왜? 사용하는지

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("멍멍멍");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("동물 소리 테스트 종료");
}
  • 클래스 마다 sound() 메서드를 따로 만들고 호출하는 코드가 중복된다.
  • 새로운 동물이 추가될 때마다 같은 구조의 코드가 반복된다.

중복제거

메서드 사용

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

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

배열, for문 사용

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

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

다형성 사용

부모 클래스

1
2
3
4
5
6
7
package poly.ex2;

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

자식 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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("야옹야옹");
    }
}

메인 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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("동물 소리 테스트 종료");
    }
}
  • 공통 부모 클래스 Animal 을 만들고
  • 자식 클래스가 Animal 을 상속 + 오버라이딩
  • 모든 동물을 Animal 타입으로 다룰 수 있음!
  • 덕분에 배열과 for문을 활용할 수 있게 된다.
1
2
3
4
5
6
7
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

  • 다형적 참조 덕분에 animal의 변수 자식인 Dog , Cat , Caw 의 인스턴스를 참조할 수 있다. (부모 → 자식 가능)
  • 메서드 오버라이딩 덕분에 animal.sound() 를 호출해도 Dog.sound() , Cat.sound() ,Caw.sound() 와 같이 각 인스턴스의 메서드를 호출할 수 있다
  • Dog, Cat, Caw 같은 구체 클래스가 아닌 부모 타입 Animal만 사용해서 코드를 유연하게 설계할 수 있다.
  • 새로운 동물을 추가해도 soundAnimal() 메서드는 코드 변경 없이 재사용 가능하다.
  • 이런 코드는 유지보수가 쉽고, 확장성도 높다.

하지만 문제점이 있다.

  1. Animal을 직접 생성할 수 있다.
1
Animal animal = new Animal(); // 이상하지만 가능함
  • Animal은 개념적으로 추상적인 “동물”이지, 실제 동물은 아님
  • 그런데 클래스이기 때문에 누군가 실수로 인스턴스를 생성할 수도 있다
  1. sound() 오버라이딩을 누락할 수도 있다.
1
2
3
public class Pig extends Animal {
    // sound() 오버라이딩 안 함
}
  • 컴파일 에러는 없지만, 실행 시 Animal.sound()가 호출됨 → 예상치 못한 결과 발생

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

추상 클래스

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

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

  • Animal은 추상적인 개념인데 new Animal()이 가능하다 → ❌
  • sound() 오버라이딩을 누락해도 문제 없이 컴파일 된다 → ❌
  • 실수로 잘못 사용해도 오류가 발생하지 않음 → 위험한 코드

따라서

  • Animal추상 클래스로 선언하고, sound()추상 메서드로 만들면,
  • 자식 클래스는 반드시 sound()를 오버라이딩해야 한다.
1
2
3
4
5
6
7
8
9
public abstract class AbstractAnimal {

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

    public void sleep() {
        System.out.println("Sleep");
    }
}
  • abstract 키워드를 사용하면 인스턴스 생성 불가
  • sound() , eat() 을 추상 메서드로 선언해 강제 오버라이딩 가능
  • sleep() 같은 일반 메서드는 선택적 오버라이딩 가능

자식 클래스

1
2
3
4
5
6
7
8
9
10
11
public class Dog extends AbstractAnimal {
    @Override
    public void sound() {
            System.out.println("Dog.sound");
    }

    @Override
    public void eat() {
        System.out.println("Dog.eat");
    }
}
1
2
3
4
5
6
7
8
9
10
11
public class Cow extends AbstractAnimal {
    @Override
    public void sound() {
        System.out.println("Cow.sound");
    }

    @Override
    public void eat() {
        System.out.println("Cow.eat");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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");
    }
}

메인 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

순수 추상 클래스

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

1
2
3
4
public abstract class Animal {
    public abstract void sound();
    public abstract void move();
}
  • 모든 자식 클래스는 모든 메서드를 구현해야 함
  • 인스턴스를 생성할 수 없음
  • 실행 로직을 가지고 있지않음 다형성을 위한 부모 타입으로서 껍데기 역할 제공

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

인터페이스

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

  • 추상 클래스도 좋지만, 다음과 같은 한계가 존재한다
    • new를 막을 수는 있지만 실행 메서드가 끼어들 수 있음
    • 다중 상속(다중 구현)이 불가능
  • 순수한 설계도로서 기능만 제공하고, 강제성 + 유연성이 모두 필요한 경우에는 인터페이스가 더 적합하다.

인터페이스란?

  • interface 키워드로 선언
  • 모든 메서드는 기본적으로 public abstract
  • 메서드 몸체가 없고, 반드시 자식 클래스가 오버라이딩 해야 함
  • 인스턴스 생성 불가능
1
2
3
public interface InterfaceAnimal {
    public void sleep();
}
  • implements를 사용해 인터페이스 구현
  • 모든 메서드를 반드시 오버라이딩해야 컴파일 오류가 없음

자식 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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");
    }
}

메인 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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---

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

메서드 규약 = “설계도”

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

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

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

다형성과 인터페이스

1
2
3
4
5
private static void sleep(InterfaceAnimal animal) {
        System.out.println("---sleep Start");
        animal.sleep();
        System.out.println("sleep End---");
    }
  • Dog, Cat, Cow 등 모두 Animal 인터페이스를 구현
  • 하나의 sleep() 메서드로 다양한 객체 처리 가능
  • 새로운 동물이 추가돼도 기존 코드는 변경 없이 재사용 가능

인터페이스의 장점

  • 제약을 줄 수 있다 → 반드시 구현해야 하는 규약 제공
  • 다형성을 극대화할 수 있다 → 하나의 타입으로 모든 구현체를 처리
  • 다중 구현 가능 → 유연한 설계 가능

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

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

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

인터페이스 다중 구현

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

  • 클래스의 다중 상속은 다이아몬드 문제 발생 가능성
  • 예: 부모 A, B 둘 다 move()를 가지고 있고, 자식 C가 상속받으면 어떤 move()를 호출할지 모호함
  • 이런 구조는 복잡하고 유지보수도 어려워짐

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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");
    }
}

메인 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
  • sendData()Wifi, Bluetooth 양쪽에 있지만
  • Phone에서 하나만 구현하면 끝
  • 실제로 호출되는 건 전부 Phone.sendData()

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

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

클래스와 인터페이스 활용

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

  • 클래스 상속과 인터페이스 구현을 동시에 사용하는 구조
  • 공통 기능은 추상 클래스, 개별 기능은 인터페이스로 분리
  • 다양한 자동차의 기능을 유연하게 표현하고 확장 가능하게 설계

추상 클래스

1
2
3
4
5
6
7
8
9
public abstract class Car {
    //abstract 추상 클래스
    public abstract void startEngine();
    public abstract void stopEngine();

    public void hook() {
        System.out.println("Car hook");
    }
}
  • 공통 기능 제공
  • 객체 생성을 제한하고 상속을 통해 구현 유도

인터페이스

1
2
3
4
5
6
7
public interface Electric {
    void charge();
}

public interface AutoDrive {
    void autoDrive();
}
  • 특화된 기능은 인터페이스로 분리
  • 자동차는 다 전기차는 아니고, 다 자율주행 가능한 것도 아니니까!

클래스 - 아반떼

1
2
3
4
5
6
7
8
9
10
11
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");
    }
}
  • 추상 클래스만 상속
  • 전기차도, 자율주행차도 아님

클래스 - 테슬라

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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");
    }
}
  • Car 상속 + Electric, AutoDrive 인터페이스 다중 구현
  • 필요 기능만 오버라이딩

메인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
  • Car 추상 클래스는 공통 구조 제공 (템플릿 역할)
  • Electric, AutoDrive 인터페이스는 기능 단위 분리 (역할 기반 설계)
  • Tesla는 상속 + 다중 구현으로 복합 기능 수행
  • Avante는 단일 기능 차량 (자율주행/전기 기능 없음)

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

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


출처: 김영한의 실전 자바 - 기본편

This post is licensed under CC BY 4.0 by the author.

Trending Tags