다형성(polymorphism)
by mignon25
객체지향 프로그래밍 언어 Java에서 다형성이란?
한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 하는 것.
다시 말해, 상위 클래스 타입의 참조변수를 통해서 하위 클래스 객체를 참조할 수 있도록 허용한 것.
Example.
package Polymorphism;
public class FoodTest {
public static void main(String[] args) {
// 객체(우) 타입과 참조변수(좌) 타입 일치
Food food1 = new Food();
Chocolate chocolate = new Chocolate();
// 객체(우) 타입과 참조변수(좌) 타입 불일치 (다형성 적용)
Food food2 = new Chocolate();
// Chocolate chocolate2 = new Food(); // 에러.
food1.foodInfo();; // I'm Food.
chocolate.foodInfo(); // I'm Chocolate.
food2. foodInfo(); // I'm Chocolate.
// (실제 참조 객체에서 오버라이딩 된 메서드 실행)
// food2.name; // 에러.
// (참조변수 타입이 Food 이므로 Chocolate의 멤버인 name 참조 불가)
System.out.println(chocolate.name); // Chocolate
}
}
class Food {
public void foodInfo() {
System.out.println("I'm Food.");
}
}
class Chocolate extends Food {
String name = "Chocolate"; // Chocolate 클래스에서 추가한 멤버
public void foodInfo() { // 오버라이딩
System.out.println("I'm Chocolate.");
}
}
10번 줄이 위에서 말한 내용이 적용된 예시이다.
Food food2 = new Chocolate();
참조변수의 타입은 상위 클래스 타입인 Food 이지만, 이 변수가 실제로 가리키는 객체는 하위클래스인 Chocolate 클래스의 인스턴스이다.
이 때 사용할 수 있는 멤버의 개수는 참조변수의 타입에 따라 달라진다.
7번 줄처럼 Chocholate 객체를 Chocolate 타입의 참조변수로 참조한 경우에는 name멤버를 사용할 수 있다. (21번 줄 : 정상 출력)
그러나 10번 줄의 food2는 실제로 참조하고 있는 인스턴스는 Chocolate이지만 참조변수의 타입은 상위 클래스 타입인 Food 타입이므로 Chocolate의 멤버인 name을 사용할 수 없다. (20번 줄 : 에러 발생)
결과적으로 상위 클래스의 참조변수로 하위 클래스 객체를 참조할 경우 사용할 수 있는 멤버의 수는 줄어들게 된다.
그런데 여기서 중요한 것은 사용할 수 있는 멤버의 수가 아니라 상위클래스의 참조변수로 하위클래스의 객체를 참조할 수 있다는 사실이다.
(이를 통해 여러 타입의 객체를 오버로딩 없이 하나의 매개변수로 처리한다든지, 하나의 배열로 묶는다든지 이런 작업을 수행할 수 있게 되기 때문.)
또 한 가지 기억해야 할 부분이 있다.
하위 클래스 타입의 참조변수로 상위 클래스 타입의 객체를 참조할 수는 없다. (9번 줄)
참조변수 자체가 사용할 수 있는 멤버가 더 많은데 객체가 그 멤버를 가지고 있지 않기 때문이다.
그리고 메서드의 경우 참조변수의 타입이 뭐가 됐든 실제 객체에서 오버라이딩 된 내용으로 실행된다는 점도 기억해두자.
(15, 17번 줄 결과 동일)
참조변수의 타입 변환
1. 서로 상속관계에 있는 상위 클래스 - 하위 클래스 사이에만 타입 변환이 가능하다.
2. 하위 클래스 타입 -> 상위 클래스 타입으로 타입 변환(업캐스팅) : 형변환 연산자(괄호) 생략 가능
3. 상위 클래스 타입 -> 하위 클래스 타입으로 타입 변환(다운캐스팅) : 형변환 연산자(괄호) 반드시 명시
package Polymorphism;
public class CastingTest {
public static void main(String[] args) {
Dog dog = new Dog();
Animal animal = dog; // 상위 클래스 Animal 타입으로 변환 (생략 가능)
Dog dog2 = (Dog) animal; // 하위 클래스 Dog 타입으로 변환 (생략 불가)
// Cat cat = (Cat) dog; // 에러! 상속 관계가 아니므로 타입 변환 불가
}
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
이 부분도 참... 직관적으로 다가오지 않는 내용 중의 하나인 것 같다.
그래도 어떻게든 오래 기억하기 위해 내 나름대로 이해한 바는 이렇다.
=> 변환이 자연스럽게 이루어지지 않는다면 연산자가 필요하다.
위에서 언급했듯이, 참조변수의 타입에 따라 사용할 수 있는 멤버의 수가 달라진다.
하위 클래스의 참조변수가 사용할 수 있는 멤버의 수가 더 많으므로, 하위클래스 타입의 참조변수에서 상위클래스 타입의 참조변수로 업캐스팅하는 건 그냥 사용할 수 있는 멤버를 상위 클래스 타입에 맞게 줄이기만 하면 된다.
반면 상위 클래스의 타입의 참조변수에서 하위 클래스 타입의 참조변수로 변환하려면 없는 멤버를 늘려야 하는 상황이다.
그러므로 연산자의 명시가 필요한 게 아닐까? 라는 개인적인 생각.
instanceof 연산자
참조 변수의 타입 변환(캐스팅)이 가능한지 여부를 boolean 타입으로 확인할 수 있다.
참조_변수 instanceof 타입
캐스팅 가능 여부를 판단하기 위해서는 객체를 '어떤 클래스의 생성자로 만들었는지'와 '클래스 사이에 상속관계가 존재하는지'를 판단해야 한다.
이 때 instanceof 연산자를 통해 손쉽게 확인할 수 있다.
package Polymorphism;
public class InstanceofTest {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal instanceof Object); // true
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Dog); // false
System.out.println(animal instanceof Cat); // false
Animal cat = new Cat();
System.out.println(cat instanceof Object); // true
System.out.println(cat instanceof Animal); // true
System.out.println(cat instanceof Cat); // true
System.out.println(cat instanceof Dog); // false
}
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
참조변수의 형변환은 변수가 실제로 가리키고 있는 객체의 타입보다 하위 클래스 타입으로는 불가능하다.
(같거나 상위 클래스 타입으로만 가능)
Animal 클래스로부터 생성된 객체를 가리키고 있는 참조변수 animal 은 Animal 타입 또는 그보다 상위 클래스 타입으로 형변환이 가능하다.
그러므로 Animal, Object에 대해서만 true. Dog와 Cat은 그보다 하위 클래스이므로 false.
Cat 클래스로부터 생성된 객체를 가리키고 있는 참조변수 cat 은 참조변수 타입은 Animal이지만 가리키고 있는 객체가 Cat이므로 Cat 과 같거나 그보다 상위 클래스 타입으로 형변환이 가능하다.
그러므로 Cat, Animal, Object에 대해 true. Dog 클래스와는 상속관계가 아니므로 false.
멤버변수가 중복으로 선언된 경우
멤버변수의 경우 참조변수의 타입에 따라 달라진다.
(오버라이딩된 메서드는 실제 인스턴스에 따라 결정된다.)
멤버변수가 중복선언된 상태라면,
어떤 참조변수가 실제로 가리키고 있는 객체가 뭐든 간에 참조변수의 타입에서 선언된 멤버변수를 참조한다.
package Polymorphism;
public class BindingTest {
public static void main(String[] args) {
Parent parent = new Child();
Child child = new Child();
System.out.println("parent.x = " + parent.x); // 100
System.out.println("child.x = " + child.x); // 200
parent.method(); // Child Method
child.method(); // Child Method
}
}
class Parent {
int x = 100;
void method() {
System.out.println("Parent Method!");
}
}
class Child extends Parent {
int x = 200; // 멤버 변수 x 중복 선언
void method() { // 메서드 오버라이딩
System.out.println("Child Method");
}
}
// 출력 결과
parent.x = 100
child.x = 200
Child Method
Child Method
매개변수의 다형성
참조변수의 다형적인 특징은 메서드의 매개변수에도 적용된다.
카페에서 커피를 구입하는 상황이라고 가정해보자.
class Coffee {
int price;
}
class Americano extends Coffee {}
class CaffeLatte extends Coffee {}
class Customer {
int money = 50000;
void buy(Americano a){}
void buy(CaffeLatte c) {}
}
고객은 음료를 사는 기능을 가지고 있다.
그러나 지금의 방식은 음료가 추가될 때마다 매번 메소드를 오버로딩해서 추가해주어야 한다.
package Polymorphism;
public class PolyArgumentTest {
}
class Coffee {
int price;
public Coffee(int price) {
this.price = price;
}
}
class Americano extends Coffee {
public Americano() {
super(4000);
}
public String toString() { return "아메리카노"; }
}
class CaffeLatte extends Coffee {
public CaffeLatte() {
super(5000);
}
public String toString() { return "카페라떼"; }
}
class Customer {
int money = 50000;
void buy(Coffee coffee){
if(money < coffee.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money = money - coffee.price;
System.out.println(coffee + "를 구입했습니다.");
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Customer c = new Customer();
c.buy(new Americano());
c.buy(new CaffeLatte());
System.out.println("현재 잔액은 " + c.money + "원 입니다.");
}
}
//출력 결과
아메리카노를 구입했습니다.
카페라떼를 구입했습니다.
현재 잔액은 41000원 입니다.
buy() 의 매개변수 타입을 상위 클래스 타입인 Coffee 타입으로 설정하면 상위클래스의 참조변수가 하위 클래스 타입의 객체를 참조할 수 있으므로 각각의 하위 클래스인 Americano와 CaffeLatte를 일일이 지정하지 않고 Coffee라는 매개변수 하나로 모두 참조할 수 있다.
나중에 음료의 종류가 늘어나더라도 Coffee 클래스를 상속한 클래스를 추가하기만 하면 되고, Customer 클래스에서는 수정이 필요하지 않다.
'Java' 카테고리의 다른 글
| 제어자 - static, final, abstract (0) | 2023.02.28 |
|---|---|
| 추상화 - 추상클래스와 인터페이스 (0) | 2023.02.28 |
| [캡슐화] 접근 제어자(access modifier)와 getter, setter 메서드 (0) | 2023.02.25 |
| 오버라이딩(overriding), super, super() (feat. this, this()) (0) | 2023.02.25 |
| Inner Class - Instance, Static, Local (0) | 2023.02.24 |
블로그의 정보
Mignon'S Dev Log
mignon25