[자바기초.018] 다형성(Polymorphism)
[자바기초.018] 다형성(Polymorphism)
[1] 다형성(Polymorphism)이란?
- 다형성(Polymorphism)은 poly + morphism의 합성어이다.
- poly: 많은(many)
- morphism: form(형태)
- 즉, 다형성(Polymorphism)은 "많은 형태(many form)"라는 뜻으로 직역된다.
- 자바(Java)에서 다형성(polymorphism)이란, 하나의 객체(object)가 여러 가지 타입(type)을 가질 수 있는 것을 의미합니다.
- 다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나입니다.
- 자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.(ex: Parent p = new Child(); )
[2] 다형성의 예시
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Parent { ... } class Child extends Parent { ... } ... Parent pa = new Parent(); // 허용 Child ch = new Child(); // 허용 Parent pc = new Child(); // 허용 Child cp = new Parent(); // 오류 발생. | cs |
- 특정 타입의 참조 변수로는 당연히 같은 타입의 인스턴스를 참조할 수 있습니다.
- 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수와 같기 때문입니다.
- 그리고 부모 클래스 타입의 참조 변수로도 자식 클래스 타입의 인스턴스를 참조할 수 있습니다. 이유는, 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 적기 때문입니다.
- 하지만 반대의 경우인 자식 클래스 타입의 참조 변수로는 부모 클래스 타입의 인스턴스를 참조할 수 없습니다.
- 참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 많기 때문입니다.
[중요]
클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없으므로, 자식 클래스에서 사용할 수 있는 멤버의 개수가 언제나 부모 클래스와 같거나 많게 됩니다.
[3] 참조 변수의 타입 변환
자바에서는 참조 변수도 다음과 같은 조건에 따라 타입 변환을 할 수 있습니다.
1. 서로 상속 관계에 있는 클래스 사이에만 타입 변환을 할 수 있습니다.
2. 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은 생략할 수 있습니다.
3. 하지만 부모 클래스 타입에서 자식 클래스 타입으로의 타입 변환은 반드시 명시해야 합니다.
참조 변수의 타입 변환도 기본 타입의 타입 변환과 마찬가지로 타입 캐스트 연산자(())를 사용합니다.
<타입 캐스트 연산자 문법>
(변환할타입의클래스이름) 변환할참조변수
다음 예제는 참조 변수의 타입 변환을 보여주는 예제입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Parent { ... } class Child extends Parent { ... } class Brother extends Parent { ... } ... Parent pa01 = null; Child ch = new Child(); Parent pa02 = new Parent(); Brother br = null; pa01 = ch; // pa01 = (Parent)ch; 와 같으며, 타입 변환을 생략할 수 있음. br = (Brother)pa02; // 타입 변환을 생략할 수 없음. br = (Brother)ch; // 직접적인 상속 관계가 아니므로, 오류 발생. | cs |
[4] instance of 연산자
- 이러한 다형성으로 인해 런타임에 참조 변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요성이 생깁니다.
- 자바에서는 instanceof 연산자를 제공하여, 참조 변수가 참조하고 있는 인스턴스의 실제 타입을 확인할 수 있도록 해줍니다.
자바에서 instanceof 연산자는 다음과 같이 사용합니다.
<instanceof 문법>
참조변수 instanceof 클래스이름
- 왼쪽에 전달된 참조 변수가 실제로 참조하고 있는 인스턴스의 타입이 오른쪽에 전달된 클래스 타입이면 true를 반환하고, 아니면 false를 반환합니다.
- 만약에 참조 변수가 null을 가리키고 있으면 false를 반환합니다.
다음 예제는 참조 변수가 실제로 가리키고 있는 인스턴스의 타입을 instanceof 연산자로 확인하는 예제입니다.
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 | class Parent { } class Child extends Parent { } class Brother extends Parent { } public class Polymorphism01 { public static void main(String[] args) { Parent p = new Parent(); System.out.println(p instanceof Object); // true System.out.println(p instanceof Parent); // true System.out.println(p instanceof Child); // false System.out.println(); Parent c = new Child(); System.out.println(c instanceof Object); // true System.out.println(c instanceof Parent); // true System.out.println(c instanceof Child); // true } } | cs |
[예제1] 아래 코드를 보며 Shape 클래스의 다형성을 확인해 보세요.
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 37 38 39 40 41 42 | class Shape { public void what() { System.out.print("Shape "); } } class Rectangle extends Shape { public void what() { System.out.print("Rectangle "); } } class Square extends Rectangle { } class Oval extends Shape { public void what() { System.out.print("Oval "); } } class Circle extends Oval { public void what() { System.out.print("Circle "); } } public class Main { public static void main(String[] args) { // example of array: int[] a = {1,2,3}; Shape[] shapes = {new Shape(), new Rectangle(), new Square(), new Circle()}; for (Shape s : shapes) { s.what(); } /* same result above for(int i = 0; i < shapes.length; i++) { shapes[i].what(); } */ } } | cs |
[실행결과]
- Shape object는 "Shape"을 출력한다.
- Rectangle object는 "Rectangle"을 출력한다.
- Square object는 오버라이드(Override)된 what 메소드가 없어서, 부모 클래스인 Rectangle의 what 메소드를 실행해 "Rectangle"을 출력한다.
- Circle object는 "Circle"을 출력한다.
[유제1] 아래 코드의 출력결과를 말해 보세요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Student { public String getFood() { return "Pizza"; } public String getInfo() { return this.getFood(); } } class GradStudent extends Student { public String getFood() { return "Taco"; } } public class Main { public static void main(String[] args) { Student s1 = new GradStudent(); System.out.println(s1.getInfo()); } } | cs |
[유제2] 아래 코드의 실행 결과를 말해보자.
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 37 | class Car { private int fuel; public Car() { fuel = 0; } public Car(int g) { fuel = g; } public void addFuel() { fuel++; } public void display() { System.out.print(fuel + " "); } } class RaceCar extends Car { public RaceCar(int g) { super(2 * g); } } public class Main { public static void main(String[] args) { Car car = new Car(5); Car fastCar = new RaceCar(5); car.display(); car.addFuel(); car.display(); fastCar.display(); fastCar.addFuel(); fastCar.display(); } } | cs |
[예제2] 아래 코드에서 Book b = new Dictionary() 이 실행된다고 가정할 때, compile-time에서 에러가 발생 할 수 있는 명령어는 아래 A, B, C 중 어떤것일까?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class Book { public String getISBN() { // implementation not shown } // constructors, fields, and other methods not shown } public class Dictionary extends Book { public String getDefinition() { // implementation not shown } } | cs |
- 정답: B
- 이유: compile-time에서는 Book 클래스 타입의 변수 b가 선언만 되었기 때문에, Book 클래스가 getDefinition()이라는 메소드를 가지고 있지도 않고 또한 그런 메소드를 상속 조차 받은것도 아니므로, compile-time에서 B는 에러가 발생합니다.
- run-time에서는 b object가 실제로 만들어지기 때문에 b.getDefinition()이 에러없이 잘 실행됩니다.C는, b object를 Dictionary 클래스 타입으로 변경(casting)하여, Dictionary 클래서는 getDefinition()메소드가 존재하기 때문에 에러가 발생하지 않습니다.
1.declared type - The type that was used in the declaration. List aList = new ArrayList() has a declared type of List. This is used at compile-time to check that the object has the methods that are being used in the code.
2.run-time type - The type of the class that created the object. List aList = new ArrayList() has a run-time type of ArrayList. This is used at run-time to find the method to execute.
[유제 정답은 아래 "더보기" 클릭]
[유제1 정답]
(이유: 만들어진 s1 object는 GradStudent 클래스의 object이므로, "this.getFood() "의 this는 GradStudent의 object 이므로, "Taco" 가 출력된다.)
[유제2 정답]
(이유: Car 클래스를 RaceCar 클래스가 상속 받았기 때문에, RaceCar의 object는 Car 클래스의 public 메소드를 모두 사용할 수 있다.)