Java

다형성 (2) - 추상클래스, 인터페이스

공부처음하는사람 2024. 6. 12. 20:50

 

다형성 활용

이전 파트에서 이해하지 못했던 다형적 참조를 어떤경우에 사용하는지 알 수 있다.

동물의 울음소리 문제로, 다형성을 사용하지 않은 코드이다.

package poly.ex1;

public class AnimalSoundMain {

    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("동물 소리 테스트 시작");
        cow.sound();
        System.out.println("동물 소리 테스트 종료");

        System.out.println("동물 소리 테스트 시작");
        cat.sound();
        System.out.println("동물 소리 테스트 종료");
    }
}
// 코드가 중복된다.

 

이 부분의 중복을 제거하기 위해 메서드, 배열과 for문을 사용해 중복 제거를 시도해보겠다.

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

 

soundCow 메서드의 매개변수는 Cow 전용 메서드가 되므로 Dog, Cat은 인수로 사용할 수 없다.

 

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

 

Arr엔 서로 다른 타입을 담을 수 없다. 또한 여기서 동물이 추가 된다면 더 많은 중복코드가 발생할 것이다.

이를 방지하기 위해 다형성을 활용한다.

 

다형성의 활용2

Dog, Cat, Cow의 상위 개념인 Animal 부모 클래스를 만들고 상속관계를 사용하면 코드 중복을 줄일 수 있다.

출처: 김영한 실전자바 basic - 다형성

 

package poly.ex2;

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(duck);
        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(cow);

    }

    private static void soundAnimal(Animal animal) {

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

 

 

 

soundAnimal 메서드의 매개변수 Animal animal은 dog, cat, cow를 담고 있기때문에 각각의 인스턴스를 참조할 수 있다.

그 결과 코드 중복을 제거할 수 있었다.

 

 

개선된 코드

package poly.ex2;

public class AnimalPolyMain3 {

    public static void main(String[] args) {
        Animal[] animalArr = {new Dog(),new Cat(), new Cow(), new Pig()};

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

    private static void soundAnimal(Animal animal) {
        System.out.println("동물 소리 테스트 시작");
        animal.sound();
        System.out.println("동물 소리 테스트 시작");
    }
}
package poly.ex2;

public class Animal {

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

 

코드 중복을 많이 줄일 수 있었다. 그러나 이 코드엔 2가지 문제점이 남아있는데,

1. Animal 클래스를 생성할 수 있음

2. Animal 클래스의 sound( ) 메서드를 오버라이딩 하지 않을 가능성 

 

이 문제를 해결하는 추상클래스가 있다.

 

추상 클래스

동물(Animal)과 같이 실제 생성되면 안되는 클래스를 추상 클래스라 한다. 동물은 추상적인 개념이기에 실체인 인스턴스가

존재하지 않는다. 대신에 상속을 목적으로 사용되고 부모 클래스 역할을 담당한다.

 

- 추상 클래스엔 abstract 키워드를 사용한다. ex) public abstract class Animal {......}

 

추상 메서드

- 자식 클래스가 반드시 오버라이딩 해야 하는 메서드를 부모 클래스에서 정의할 수 있다. 클래스와 마찬가지로 abstract 키워드를

사용한다. ex) public abstract void sound( );

- 추상 메서드는 바디가 없다. 따라서 자식 클래스에서 구현을 해야한다.

- 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다.

 

 

순수한 추상 클래스

package poly.ex4;

public abstract class AbstractAnimal {

    public abstract void sound();
    public abstract void move();
}

 

 

추상 클래스에 모든 메서드가 추상 메서드이다. 이 클래스는 단지 다형성을 위한 부모 타입으로 껍데기 역할만 하고 있는 셈이다.

 

- 인스턴스를 생성할 수 없다.

- 상속시 자식은 모든 메서드를 오버라이딩 해야한다.

- 주로 다형성을 위해 사용된다.

 

이 특징을 보면 순수 추상 클래스는 어떤 규격을 지켜서 구현해야 한다. 이것은 인터페이스와 같다.

(자바엔 순수 추상 클래스라는 용어가 없다. 순수 추상 클래스 == 인터페이스)

 

 

인터페이스

자바에서 순수 추상 클래스를 더욱 편리하게 사용할 수 있는 인터페이스 기능이 있다.

 

인터페이스의 특징

- 인터페이스의 멤버 변수는 public, static, final이 모두 포함되어 있다고 간주된다. 따라서 모든 필드는 상수로 설정이 된다.

 

출처: 김영한의 실전자바 basic - 다형성

 

나머지 내용은 순수한 추상 클래스와 유사하다. 순수 추상클래스가 인터페이스가 되었을 뿐이라고 생각하면 이해가 쉽다.

 

상속 vs 구현

부모 클래스를 상속 받을 때, 상속 받는다고 표현하지만 인터페이스를 상속받을 땐 인터페이스를 구현한다고 표현한다.

상속은 부모의 기능을 물려받는것이 목적인데, 인터페이스는 기능을 자식클래스에서 오버라이딩 후 직접 구현을 해야하기 때문에

상속이라고 표현하기 어렵다.

 

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

- 제약 : 인터페이스는 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해야하는 제약을 주는것이다. 순수 추상 클래스의 경우엔 추상 메서드가 아닌 메서드를 정의할 수 있다. 그렇게 되면 순수한 추상 클래스가 아니게 된다. 인터페이스는 이 문제를 원천 차단할 수 있다.

- 다중 구현: 자바에서는 하나의 부모만 상속받을 수 있는데, 인터페이스는 다중 구현(상속)이 가능하다.

 

인터페이스 - 다중 구현

출처: 김영한의 실전자바 basic - 다형성

다중 상속 그림이다. 이 그림대로라면 airplane의 move( ) 메서드와 Car의 move( ) 메서드 둘 중 어떤걸 사용해야할까? 애매한 상황이 발생한다. 이 문제를 다이아몬드 문제라고 한다. 그리고 다중상속은 계층구조가 매우 복잡해질 수 있어 자바에선 이를 허용하지 않는다.

 

출처: 김영한의 실전자바 basic - 다형성

위 그림은 다중 구현 그림이다. 다중상속처럼 생각해보면 둘 중 어떤 클래스의 methodCommon( ) 메서드를 호출해야할까?

둘 중 어떤것을 호출해도 상관없다. 왜냐면 추상메서드로 이루어져있기 때문에 Child에서 구현해주면 끝인 상황이다.

출처: 김영한의 실전자바 basic - 다형성

 

항상 오버라이딩 된 메서드가 우선순위라는걸 생각하면 이해하기 쉽다.

 

 

클래스와 인터페이스 활용

출처: 김영한의 실전자바 basic - 다형성

 

클래스와 인터페이스를 활용하면 위와 같이 메서드를 상속받을 수 있다.

 

package poly.ex6;

public class Bird extends AbstractAnimal implements Fly{
    @Override
    public void fly() {
        System.out.println("새가 날아갑니다.");
    }

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

(코드 작성 예시)

 

 


 

크게 어려운 개념은 아닌 것 같다. 기초부분이라 그렇겠지만 메서드 호출이 어떤식으로 작동하는지 이해만 한다면 인터페이스까지의 내용은

어려운 점이 없었다. 객체지향 공부가 제일 재밌는것 같다

'Java' 카테고리의 다른 글

이것이자바다 - 배열  (1) 2024.06.15
다형성(3) - OCP  (0) 2024.06.13
다형성 (캐스팅, 메서드 오버라이딩)  (1) 2024.06.10
상속  (0) 2024.06.07
final  (0) 2024.06.06