다형성 적용하기
다형성을 이용하면 다양한 타입을 하나의 타입으로 묶어서 코드를 더 간결하게 만들 수 있다.
- 다형성 적용 전의 코드
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("동물 소리 테스트 종료");
}
- 다형성 적용 후 코드
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Cow cow = new Cow();
soundAnimal(dog);
soundAnimal(cat);
soundAnimal()cow;
}
//동물이 추가 되어도 변하지 않는 코드
private static void soundAnimal(Animal animal) {
System.out.println("동물 소리 테스트 시작"); animal.sound();
System.out.println("동물 소리 테스트 종료");
}
Dog, Cat, Cow를 Animal의 자식 클래스로 만듦으로써 모두 Animal 타입으로 업캐스팅이 가능해졌고, soundAnimal(Animal animal)로 간단하게 표현할 수 있게 되었다.
- 리팩토링
public static void main(String[] args) {
Animal[] animalArr = {new Dog(), new Cat(), new Caw()};
for (Animal animal : animalArr) {
soundAnimal(animal);
}
}
//동물이 추가 되어도 변하지 않는 코드
private static void soundAnimal(Animal animal) {
System.out.println("동물 소리 테스트 시작"); animal.sound();
System.out.println("동물 소리 테스트 종료");
}
새로운 기능이 추가되었을 때 변하는 부분을 최소화하는 것이 잘 작성된 코드이다. 그러기 위해선 코드에서 변하는 부분과 변하지 않는 부분을 명확하게 구분하자.
남은 문제
다형성을 적용해 위 코드를 간결하게 만들었지만 두 가지 문제가 남아있다.
- Animal 클래스 인스턴스를 생성할 수 있는 문제
- Animal 클래스를 상속 받는 곳에서 sound() 메서드 오버라이딩을 하지 않을 가능성
1. Animal 클래스 인스턴스 생성할 수 있는 문제
개, 고양이, 소가 실제 존재하는 것은 당연하지만, 동물이라는 추상적인 개념이 실제 존재하는 것은 이상하게 느껴진다. 실수로 new Animal()을 사용해 Animal 인스턴스를 만들면 제대로 수행하지 않는 인스턴스가 생성될 수 있다.
2. Animal의 하위 클래스에서 메서드 오버라이딩을 하지 않을 가능성
Animal을 상속한 하위 클래스에서 깜빡하고 메서드를 오버라이딩하지 않는다면 의도한 결과가 나오지 않을 수 있다. 예시로 pig.sound()를 호출하고 “꿀꿀”을 의도했는데, pig에 sound()가 없어서 Animal의 sound()가 호출이 될 수 있다.
이 두가지 문제를 해결하기 위해서는 추상 클래스와 추상 메서드를 사용하면된다.
추상 클래스
추상 클래스는 부모 클래스는 제공하지만, 실제 생성되면 안되는 클래스를 의미한다. 추상 클래스는 상속을 목적으로 부모 클래스 역할을 하는 인스턴스를 생성할 수 없는 클래스이다.
추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다.
특징
abstract class AbstractAnimal {...}
- class 앞에 abstract
- 인스턴스 생성 불가능
- 하나 이상의 추상 메서드 가지고 있음
추상 메서드
추상 메서드는 부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야하는 메서드이다.
- 특징
- abstract 키워드가 붙여져 있다
- 메서드 바디가 없다
- 상속받는 자식 클래스가 무조건 오버라이딩해서 사용해야 한다
- 추상 클래스가 추상 클래스를 상속받으면 추상 메서드 오버라이딩 안 해도 됨
정리
- 추상 클래스로 Animal 인스턴스 생성 문제를 근본적으로 방지해준다
- 추상 메서드로 부모 메서드를 오버라이딩 하지 못할 문제를 방지해준다
순수 추상 클래스
순수 추상 클래스는 추상 메서드로만 이루어진 추상 클래스를 의미한다.
- 특징
- 인스턴스 생성 불가
- 상속 시 자식은 모든 메서드를 오버라이딩 해야한다
- 주로 다형성을 위해 사용된다
순수 추상 클래스를 상속하면 모든 추상 메서드를 오버라이딩해야하기 때문에 어떤 규격을 지켜서 구현해야 하는것처럼 느껴진다. 이런 순수 추상클래스를 자바가 더 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공한다.
인터페이스
인터페이스는 앞서 설명한 순수 추상 클래스와 동일하지만 약간의 편의 기능이 추가 되었다.
- 특징
- 모든 인터페이스 메서드는 public, abstract 이다
- public abstract는 생략이 권장된다
- 다중 구현(다중 상속)을 지원한다
- public static final로 상수 선언한다
- 인터페이스를 사용해야 하는 이유
- 제약 : 인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이승의 메서드를 반드시 구현하라는 규약(제약)을 주는 것이다. 순수 추상 클래스만 사용한다면 구현 함수를 추가할 수 있는 문제가 발생할 수 있는데, 인터페이스를 사용하면 이 문제를 사전에 원천 차단할 수 있다.
- 다중 구현 : 상속은 다중 상속이 불가능한 반면에 인터페이스에는 추상 메서드로만 구성되어 있기 때문에 다이아몬드 문제가 발생하지 않아 다중 구현이 가능하다.
클래스, 추상 클래스, 인터페이스는 모두 똑같다
클래스, 추상 클래스, 인터페이스는 프로그램 코드, 메모리 구조상 모두 동일하다. 모두 .java로 정의하고, .class로 다뤄진다.
Reference
인프런 '김영한의 실전 자바 - 기본편'
'Dev Language > Java' 카테고리의 다른 글
[자바/JDBC] 스프링-DB 1편 1. JDBC의 이해 (0) | 2024.01.30 |
---|---|
[자바/기본] 12. 다형성과 설계 (0) | 2024.01.17 |
[자바/기본] 10. 다형성1 (0) | 2024.01.17 |
[자바/기본] 9. 상속 (0) | 2024.01.16 |
[자바/기본] 8. final (0) | 2024.01.16 |