본문 바로가기

Java/객체지향프로그래밍

[Java] 다형성(polymorphism) - 코딩밥상

다형성이란?

 객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다. 즉 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다.

단, 이처럼 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 경우 자손클래스에만 있는 멤버변수는 사용할 수 없다.

 

 반대로 자손 클래스 타입의 참조변수로 조상 클래스 타입의 인스턴스를 참조하는것은 가능할까?

불가능하다. 실제 인스턴스(조상)의 멤버 변수보다 참조변수(자손)가 사용할 수 있는 멤버 개수가 더 많기 때문이다.

즉 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않는 것이다.

참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.

 

참조변수의 형변환

 서로 상속관계에 있는 클래스 사이에 한해서 자손타입의 참조변수와 조상타입의 참조변수간 형변환이 가능하다.

이 때 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환을 생략할 수 있다.

자손타입 -> 조상타입 (Up-casting)      -    형변환 생략 가능
조상타입 -> 자손타입 (Down-casting) -    형변환 생략 불가

자손타입의 참조변수가 조상타입의 참조변수로 형변환 하는 것은 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고 있는 멤버의 개수보다 적을 것이 분명하므로 형변환을 생략할 수 있도록 한 것이다.

 

 주의할 점은 형변환은 참조변수의 타입을 변환하는 것일 뿐, 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위를 조절하는 것일 뿐이다.

 

 정리하면, 서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인(instanceof연산자)하는 것이 중요하다.

instanceof연산자
 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 사용한다.
주로 조건문에 사용되며 instanceof의 왼쪽에는 참조변수를, 오른쪽에는 타입(클래스명)이 피연산자로 위치한다.
연산결과로 boolean값을 반환한다.

실제 인스턴스와 같은 타입의 instanceof연산 이외에 조상 타입의 instanceof연산에도 true를 반환하며, instanceof연산의 결과가 true라는 것은 검사한 타입으로의 형변환을 해도 아무런 문제가 없다는 뜻이다.

참조변수와 인스턴스의 연결

 조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다.

 

 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만,

멤버 변수의 경우 조상타입의 참조변수를 사용했을 때는 조상 클래스의 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손클래스의 멤버변수가 사용된다. 


매개변수의 다형성

 매개변수가 조상 클래스 타입이라는 것은, 메서드의 매개변수로 자손타입의 참조변수면 받아들여진다는 뜻이다.

단, 이 경우 조상 클래스의 멤버만 접근 가능하다.

예제)

class Product{
	int price;
    int bonusPoint;
    
    Product(int price){
    	this.price = price;
        bonusPoint = (int)(price/10.0);
}

class Tv extends Product{
	Tv(){	super(100);}
}
class Computer extends Product{
	Computer{	super(200);}
}

class Buyer{
	int money = 1000;
    int bonusPoint = 0;
    
	void buy(Product p){	//매개변수로 Tv,Computer 타입의 참조변수가 들어갈 수 있다.
    	money = miney - p.price;
        bonusPoint = bonusPoint + p.bonusPoint;
    }
}

여러 종류의 객체를 배열로 다루기

 조상타입의 참조변수로 자손타입의 객체를 참조하는 것이 가능하므로, Product클래스가 Tv, Computer의 조상일 때, 다음과 같이 할 수 있다.

Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

위 코드를 Product타입의 참조변수 배열로 처리하면 아래와 같다.

Product[] p = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

이처럼 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다. 또는 묶어서 다루고 싶은 객체들의 상속관계를 따져서 가장 가까운 조상클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.