다형성
public class Animal {
public void sound(){
System.out.println("동물 울음 소리");
}
}
--------------------------------------------------------
public class AnimalPolyMain1 {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Caw caw = new Caw();
soundAnimal(dog);
soundAnimal(cat);
soundAnimal(caw);
}
private static void soundAnimal(Animal animal) {
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 종료");
}
}
1. 이 코드의 핵심은 Animal animal 부분이다. 다형적 참조 덕분에 animal 변수는 자식인 Dog , Cat , Caw의 인스턴스를 참조할 수 있다. (부모는 자식을 담을 수 있다)
메서드 오버라이딩 덕분에 animal.sound() 를 호출해도 Dog.sound() , Cat.sound(), Caw.sound()와 같이 각 인스턴스의 메서드를 호출할 수 있다. 만약 자바에 메서드 오버라이딩이 없었다면 모두 Animal의 sound()가 호출되었을 것이다.
*이번에는 배열과 for문을 사용해서 중복을 제거해보자 !
public class AnimalPolyMain2 {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Caw caw = new Caw();
Animal[] animalArr = {dog, cat, caw};
//변하지 않는 부분
for (Animal animal : animalArr) {
System.out.println("동물 소리 테스트 시작"); animal.sound();
System.out.println("동물 소리 테스트 종료");
}
}
}
*조금 더 개선해 보자!
public class AnimalPolyMain3 {
public static void main(String[] args) {
Animal[] animals = {new Dog(), new Cat(), new Caw()};
for (Animal animal : animals) {
soundAnimal(animal);
}
}
//변하지 않는 부분
private static void soundAnimal(Animal animal) {
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 테스트 종료");
}
}
**내가 메서드를 만들 부분 블록 선택하고 (커맨드 + 옵션 + m) 자동 메서드 생성
추상 클래스
추상 클래스를 사용하는 이유는 자식 클래스들이 ‘특정 메서드를 반드시 구현하도록 강제’하여 일관된 인터페이스를 보장하고 다형성을 활용할 수 있게 하기 위함이다. abstarct 키워드 사용
public abstract class AbstractAnimal {
public abstract void sound();
public void move(){
System.out.println("동물이 움직입니다");
}
}
------------------------------------------------------------
Aniaml animal = new Animal(); //동물이라는 추상적인 개념이 실제로 존재하는 것은 이상!
// 인스턴스는 제대로된 기능을 수행하지 않음!
-----------------------------------------------------------
public class Pig extends Animal {
//sound 해야하는데.. 오버라이딩 하지 않은 실수를 함!
}
정리
- 추상 클래스 덕분에 실수로 Animal 인스턴스를 생성할 문제를 근본적으로 방지해 준다.
- 추상 메서드 덕분에 새로운 동물의 자식 클래스를 만들 때 실수로 sound()를 오버라이딩 하지 않을 문제를 근본적으로 방지해 준다.
순수 추상 클래스 :모든 메서드가 추상 메서드인 추상 클래스
앞서 위의 move()도 추상 메서드로 바꿔보자.
public class Dog extends AbstractAnimal {
@Override
public void sound() {
System.out.println("멍멍"); //추상 메서드가 되었으니 오버라이딩 필수.
}
@Override
public void move() {
System.out.println("개 이동 ");
}
}
순수 추상 클래스는 단지 다형성을 위한 부모 타입으로써 껍데기 역할만 제공할 뿐.
인터페이스와 같이 느껴진다! USB 인터페이스를 생각해 보면, 순수추상 클래스가 USB 규격이고, 그 규격에 맞춰 마우스, 키보드 같은 연결 장치들을 구현할 수 있다.
자바는 이 추상 클래스를 더 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공한다.
인터페이스
인터페이스는 순수 추상 클래스와 같다. 여기에 약간의 편의 기능이 추가된다
- 인터페이스의 메서드는 모두 public, abstract이다.
- 메서드에 public abstract를 생략할 수 있다. (생략을 권장)
- 인터페이스는 다중 구현 (다중 상속)을 지원한다.
- 상속 vs 구현
부모 클래스의 기능을 자식 클래스가 상속받을 때, 클래스는 상속 받는다고 표현하지만, 부모 인터페이스의 기능을 자식이 상속 받을 때는 인터페이스를 구현한다고 표현한다. 이렇게 서로 다르게 표현하는 이유는 알아보자. 상속은 이름 그대로 부모의 기능을 물려받는 것이 목적이다.
하지만 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 물려받을 수 있는 기능이 없고, 오히려 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩 해서 기능을 구현해야 한다. 따라서 구현한다고 표현한다.
인터페이스는 메서드 이름만 있는 설계도이고, 이 설계도가 실제 어떻게 작동하는지는 하위 클래스에서 모두 구현해야 한다. 따라서 인터페이스의 경우 상속이 아니라 해당 인터페이스를 구현한다고 표현한다.
모든 메서드가 추상 메서드인 경우에 추상 클래스를 만들어도 되는데 인터페이스를 만드는 이유가 단순히 편리함에서 일까?
인터페이스를 권장하는 이유가 있을 거 같은데 알아보자.
편리함을 넘어선 다음과 같은 이유가 있다.
- 제약: 인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해라는 규약(제약)을 주는 것이다. USB 인터페이스를 생각해 보자. USB 인터페이스에 맞추어 키보드, 마우스를 개발하고 연결해야 한다. 그렇지 않으면 작동하지 않는다. 인터페이스의 규약(제약)은 반드시 구현해야 하는 것이다.
그런데, 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다. 이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 또 더는 순수 추상 클래스가 아니게 된다.
인터페이스는 모든 메서드가 추상 메서드이다. 따라서 이런 문제를 원천 차단할 수 있다. - 다중 구현: 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러 명 두는 다중구현(다중 상속)이 가능하다.
좋은 프로그램은 제약이 있는 프로그램이다!
인터페이스 - 다중구현
자바는 다중 상속을 지원하지 않는다. extends 대상은 하나만 선택 즉, 부모를 하나만 선택할 수 있다.

만약 비행기와 자동차를 상속받아서 나는 자동차를 만든다고 가정해 보자. 만약 그림과 같이 다중 상속을 이용하게 되면 AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move()를 사용해야 할지 애매한 문제가 발생. 이것을 다이아몬드 문제라고 한다.
그리고 다중 상속을 사용할 시 계층 구조가 매우 복잡해질 수 있다. 이런 문제점 때문에 자바는 클래스의 다중 상속을 허용하지 않는다. 대신에 인터페이스의 다중 구현을 허용하여 이러한 문제를 피한다.
클래스는 앞서 설명한 이유로 다중 상속이 안되는데, 인터페이스의 다중 구현은 허용한 이유는 뭘까? 바로 ‘인터페이스는 모두 추상 메서드로 이루어져 있기 때문’이다.

InterfaceA , InterfaceB는 둘다 같은 methodCommon() 을 가지고 있다. 그리고 Child 는 두 인터페이스를구현했다. 상속 관계의 경우 두 부모 중에 어떤 한 부모의 methodCommon() 을 사용해야 할지 결정해야 하는 다이아몬드 문제가 발생한다. 하지만 인터페이스 자신은 구현을 가지지 않는다. 대신에 인터페이스를 구현하는 곳에서 해당 기능을 모두 구현해야 한다.
여기서 InterfaceA , InterfaceB 는 같은 이름의 methodCommon()를 제공하지만 이것의 기능은 Child가 구현한다. 그리고 오버라이딩에 의해 어차피 Child에 있는 methodCommon() 이 호출된다. 결과적으로 두 부모중에 어떤 한 부모의 methodCommon() 을 선택하는 것이 아니라 그냥 인터페이스들을 구현한 Child 에 있는 methodCommon() 이 사용된다. 이런 이유로 인터페이스는 다이아몬드 문제가 발생하지 않는다.
따라서 인터페이스의 경우 다중 구현을 허용한다.
느낀 점 : 나는 정말 멀었다. 나는 아직 배가 고프다… (히딩크 x)
궁서체로 쓴 만큼 진지하게 너무 멀었다 더 공부하자!!!!!
'Language > ☕ Java' 카테고리의 다른 글
[Java] Object 클래스 (0) | 2024.08.19 |
---|---|
[Java] 다형성 역할과 구현 예제 (0) | 2024.08.15 |
[Java] 다형성이란? (2) | 2024.08.13 |
[Java] 상속 (0) | 2024.08.12 |
[Java] 반복문(for) (2) | 2024.08.08 |