다형성 활용1
다형성을 사용하지 않고 프로그램을 개발한 경우
package poly.ex1;
public class Dog {
public void sound() {
System.out.println("멍멍");
}
}
package poly.ex1;
public class Cat {
public void sound() {
System.out.println("냐옹");
}
}
package poly.ex1;
public class Caw {
public void sound() {
System.out.println("음매");
}
}
package poly.ex1;
public class AnimalSoundMain {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
Caw caw = new Caw();
System.out.println("동물 소리 테스트 시작");
dog.sound();
System.out.println("동물 소리 테스트 종료");
System.out.println("동물 소리 테스트 시작");
cat.sound();
System.out.println("동물 소리 테스트 종료");
System.out.println("동물 소리 테스트 시작");
caw.sound();
System.out.println("동물 소리 테스트 종료");
}
}
실행결과
동물 소리 테스트 시작
멍멍
동물 소리 테스트 종료
동물 소리 테스트 시작
냐옹
동물 소리 테스트 종료
동물 소리 테스트 시작
음매
동물 소리 테스트 종료
해당 코드에서 새로운 동물을 추가된다고 가정해보자.
소가 추가된다고 가정하면 Caw클래스를 만들고 다음 코드도 추가해야한다.
//Caw를 생성하는 코드
Caw caw = new Caw();
//Caw를 사용하는 코드
System.out.println("동물 소리 테스트 시작");
caw.sound();
System.out.println("동물 소리 테스트 종료");
Caw를 생성하는 부븐은 당연히 필요하니 상관없지만 Dog , Cat , Caw 를 사용해서 출력하는 부분이 반복된다.
이때 Dog,Cat,Caw는 서로 완전히 다른 클래스다. 문제의 핵심은 바로 타입이 다르기떄문에 종복제거 시도가 불가능하다.
다형성의 핵심은 다형적 참조와 메서드 오버라이딩을 통해 Dog , Cat , Caw 가 모두 같은 타입을 사용 하고, 각자 자신의 메서드도 호출할 수 있다.
다형성을 사용하기 위해 상속관계를 사용한다.
Dog , Cat , Caw 는 Animal 클래스를 상속받았다. 그리고 각각 부모의 sound() 메서드를 오버라이딩 한다.
package poly.ex2;
public class Animal {
public void sound() {
System.out.println("동물 울음 소리");
}
}
package poly.ex2;
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
package poly.ex2;
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("냐옹");
}
}
package poly.ex2;
public class Caw extends Animal{
@Override
public void sound() {
System.out.println("음매");
}
}
package poly.ex2;
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("동물 소리 테스트 종료");
}
}
실행결과
동물 소리 테스트 시작
멍멍
동물 소리 테스트 종료
동물 소리 테스트 시작
냐옹
동물 소리 테스트 종료
동물 소리 테스트 시작
음매
동물 소리 테스트 종료
- soundAnimal(dog)을 호출하면 soundAnimal(Animal animal)에 Dog인스턴스가 전달된다.
- Animal animal = dog로 이해하면 된다. 부모는 자식을 담을 수 있다. Animal은 Dog의 부모다.
- 메서드 안에서 animal.sound() 메서드를 호출한다.
- animal변수의 타입은 Animal이므로 Dog인스턴스에 있는 Animal클래스 부분을 찾아서 sound()메서드를 실행한다.
그런데 하위 클래스인 Dog에서 sound()메서드를 오버라이딩 했다. 따라서 오버라이딩한 메서드가 우선권을 가진다. - Dog클래스에 있는 sound() 메서드가 호출되므로 멍멍이 출력된다.
이 코드의 핵심은 Animal animal 부분이다.
- 다형적 참조 덕분에 animal 변수는 자식인 Dog,Cat,Caw의 인스턴스를 참조할 수 있다.
- 메서드 오버라이딩 덕분에 animal.sound()를 호출해도 Dog.sound(), Cat.sound() , Caw.sound() 와 같이 각 인스턴스의 메서드를 호출할 수 있다. 만약 자바에 메서드 오버라이딩이 없었다면 모 두 Animal 의 sound() 가 호출되었을 것이다.
배열과 for문을 통해 위에 코드를 개선해보면 아래처럼 된다.
package poly.ex2;
public class AnimalPolyMain3 {
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("동물 소리 테스트 종료");
}
}
지금까지 설명한 코드에는 아직 2가지 문제가 있다.
- Animal 클래스를 생성할 수 있는 문제 -> Animal()을 사용해서 Animal의 인스턴스를 생성하면 작동은 하지만 제대로된 기능을 수행하지 않는다.
- Animal클래스를 상속받는 곳에서 sound()메서드 오버라이딩을 하지 않을 가능
-> 오버라이딩 하지 않을경우 부모 클래스에 있는 Animal.sound()가 호출된다.
추상클래스
추상 클래스
동물(Animal)과 같이 부모 클래스를 제공하지만, 실제 생성되면 안되는 클래스를 추상 클래스라 한다.
추상 클래스는 이름 그대로 추상적인 개념을 제공하는 클래스이다. 따라서 실체인 인스턴스가 존재하지 않고
대신에 상속을 목적으로 사용되고, 부모 클래스 역할을 담당한다.
abstract class AbstractAnimal {...}
- 추상 클래스는 클래스를 선언할 때 앞에 추상이라는 의미의 abstract 키워드를 붙여주면 된다.
- 추상 클래스는 기존 클래스와 완전히 같다. 다만 new AbstractAnimal() 와 같이 직접 인스턴스를 생성하지 못하는 제약이 추가된 것이다.
추상 메서드
public abstract void sound();
- 추상 메서드는 선언할 때 메서드 앞에 추상이라는 의미의 abstract키워드를 붙여주면 된다.
- 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야한다.
- 추상 메서드는 상속받는 자식 클래스가 반드시 오버라이딩 해서 사용해야 한다.
- 오버라이딩 해야하기 때문에 메서드 바디 부분이 없다.
- 오버라이딩 하지 않으면 자식도 추상 클래스가 되어야 한다.
- 추상 메서드는 기존 메서드와 완전히 같다. 다만 메서드 바디가 없고, 자식 클래스가 해당 메서드를 반드시 오버라이딩 해야한다는 제약이 추가된거다.
추상 클래스2
순수 추상 클래스: 모든 메서드가 추상 메서드인 추상 클래스
순수 추상 클래스 특징
- 인스턴스를 생성할 수 없다.
- 상속시 자식은 모든 메서드를 오버라이딩 해야한다.
- 주로 다형성을 위해 사용된다.
인터페이스
자바는 순수 추상 클래스를 더 편리하게 사용할 수 있는 인터페이스 기능을 제공한다.
순수 추상클래스
public abstract class AbstractAnimal {
public abstract void sound();
public abstract void move();
}
인터페이스
public interface InterfaceAnimal {
public abstract void sound();
public abstract void move();
}
인터페이스 - public abstract 키워드 생략가능
public interface InterfaceAnimal {
void sound();
void move();
}
인터페이스 특징
- 인터페이스의 메서드는 모두 public , abstract 이다.
- 메서드에 public abstract 를 생략할 수 있다. 참고로 생략이 권장된다.
- 인터페이스는 다중 구현(다중 상속)을 지원한다
클래스 상속관계는 UML에서 실선을 사용하지만, 인터페이스 구현(상속)관계는 UML에서 점선을 사용한다.
package poly.ex5;
public interface InterfaceAnimal {
void sound();
void move();
}
package poly.ex5;
public class Dog implements InterfaceAnimal {
@Override
public void sound() {
System.out.println("멍멍");
}
@Override
public void move() {
System.out.println("개 이동");
}
}
인터페이스를 상속 받을 때는 extends 대신에 implements 라는 구현이라는 키워드를 사용해야 한다. 인터페이스 는 그래서 상속이라 하지 않고 구현이라 한다.
package poly.ex5;
public class Cat implements InterfaceAnimal {
@Override
public void sound() {
System.out.println("냐옹");
}
@Override
public void move() {
System.out.println("고양이 이동");
}
}
package poly.ex5;
public class Caw implements InterfaceAnimal {
@Override
public void sound() {
System.out.println("음매");
}
@Override
public void move() {
System.out.println("소 이동");
}
}
package poly.ex5;
public class InterfaceMain {
public static void main(String[] args) {
//인터페이스 생성 불가
//InterfaceAnimal interfaceMain1 = new InterfaceAnimal();
Cat cat = new Cat();
Dog dog = new Dog();
Caw caw = new Caw();
soundAnimal(cat);
soundAnimal(dog);
soundAnimal(caw);
}
//동물이 추가 되어도 변하지 않는 코드
private static void soundAnimal(InterfaceAnimal animal) {
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 테스트 종료");
}
}
동물 소리 테스트 시작
냐옹
동물 소리 테스트 종료
동물 소리 테스트 시작
멍멍
동물 소리 테스트 종료
동물 소리 테스트 시작
음매
동물 소리 테스트 종료
순수 추상 클래스 예제와 거의 유사하다.
클래스,추상클래스,인터페이스는 모두 똑같다.
인터페이스를 사용해야 하는 이유
- 제약:인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해라는 규약을 주는 것이다. 그런데 순수 추상 클래스의 경우 미래에 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다. 이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 더는 순수 추상 클래스가 아니게 된다. 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 이런 문제를 원천 차단 가능
- 다중 구현: 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러명 두는 다중 구현(다중 상속)이 가능하다.
인터페이스 - 다중 구현
InterfaceA , InterfaceB 는 둘다 같은 methodCommon() 을 가지고 있다. 그리고 Child 는 두 인터페이스를 구현했다. 상속 관계의 경우 두 부모 중에 어떤 한 부모의 methodCommon() 을 사용해야 할지 결정해야 하는 다이아 몬드 문제가 발생한다.
하지만 인터페이스 자신은 구현을 가지지 않는다. 대신에 인터페이스를 구현하는 곳에서 해당 기능을 모두 구현해야 한다.
여기서 InterfaceA , InterfaceB 는 같은 이름의 methodCommon() 를 제공하지만 이것의 기능은 Child 가 구현한다. 그리고 오버라이딩에 의해 어차피 Child 에 있는 methodCommon() 이 호출된다. 결과적으로 두 부모 중에 어떤 한 부모의 methodCommon() 을 선택하는 것이 아니라 그냥 인터페이스들을 구현한 Child 에 있는 methodCommon() 이 사용된다. 이런 이유로 인터페이스는 다이아몬드 문제가 발생하지 않는다. 따라서 인터페이스 의 경우 다중 구현을 허용한다.
package poly.diamond;
public interface InterfaceA {
void methodA();
void methodCommon();
}
package poly.diamond;
public interface InterfaceB {
void methodB();
void methodCommon();
}
- implements InterfaceA, InterfaceB 와 같이 다중 구현을 할 수 있다. implements 키워드 위에 , 로 여러 인터페이스를 구분하면 된다.
- methodCommon() 의 경우 양쪽 인터페이스에 다 있지만 같은 메서드이므로 구현은 하나만 하면 된다
package poly.diamond;
//인터페이스 다중 구현
public class DiamondMain {
public static void main(String[] args) {
InterfaceA a = new Child();
a.methodA();
a.methodCommon();
InterfaceB b = new Child();
b.methodB();
b.methodCommon();
클래스와 인터페이스 활용
하나의 클래스 여러 인터페이스 예시
public class Bird extends AbstractAnimal implements Fly, Swim
extends 를 통한 상속은 하나만 할 수 있고 implements 를 통한 인터페이스는 다중 구현 할 수 있기 때문에 둘이 함 께 나온 경우 extends 가 먼저 나와야 한다
'코딩' 카테고리의 다른 글
[ Java ] 실전자바-기본편 12.다형성3 (1) | 2024.02.26 |
---|---|
[ Java ] 실전자바-기본편 10.다형성1 (1) | 2024.02.22 |
[ Java ] 실전자바-기본편 9.상속 (0) | 2024.02.19 |
[ Java ] 실전자바-기본편 8.final (0) | 2024.02.16 |
[ Java ] 실전자바-기본편 7. 자바 메모리 구조와 static (2) | 2024.02.15 |