JAVA/JAVA 기본정리

다형성(polymorphism)

hyun-1200 2022. 6. 6. 01:28

📌  다형성?

: 여러 가지 형태를 가질 수 있는 능력

  •  상속과 함께 객체지향개념의 중요한 특징이며, 상속과 깊은 관계가 있다.
  • 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다.
  • 조상 클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다. 

 

⚠️ 인스턴스 타입과 일치하는 참조변수를 사용하면 인스턴스의 멤버들을 모두 사용할 수 있을 텐데 왜 조상타입의 참조변수를 사용해서 인스턴스의 일부 멤버만을 사용하도록 하는 것일까?

 

- 어떤 참조변수를 사용하느냐에 따라 사용할 수 있는 멤버의 개수가 다르다.

- 또한 조상클래스와 자손클래스에 동일한 멤버변수가 정의되어있다면, 어떤 참조변수를 사용하느냐에 따라 서로 다른 결과를 얻는다. 

(참조변수와 인스턴스의 연결 부분 참고)

 

class Tv{
	boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}
class CaptionTv extends Tv{
	String text;
    void caption() {...}
}

Tv클래스와 CaptionTV클래스가 위처럼 정의되어있을 때, 두 클래스간의 관계는 아래와 같다. 

/*참조변수 타입과 인스턴스 타입이 동일한 일반적인 인스턴스 생성 */
Tv t= new Tv();
CaptionTv c= new CaptionTv();

일반적으로 우리는 위와 같이 객체를 생성했다.

Tv인스턴스를 다루기 위해서, Tv 타입의 참조변수를 사용했다

CaptionTv 인스턴스를 다루기 위해서, CaptionTv 타입의 참조변수를 사용했다.


이처럼 인스턴스 타입과 참조변수 타입이 동일한 것이 보통이지만,

✅ 두 클래스가 상속관계에 있을 경우, 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하다.

Tv t= new CaptionTv(); // 조상 타입의 참조변수 (Tv t)로 자손 인스턴스를 CaptionTv를 참조

 

다만, 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능하다. 

( CaptionTv c= new Tv(); 는 불가능하다. ) 

  • c가 참조하고 있는 인스턴스는 Tv 타입이고, Tv타입에는 text와 caption()이 존재하지 않기 때문에 문제가 발생한다. 
  • 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다. 

 

// 두 인스턴스의 차이는 무엇일까? 
Tv t= new CaptionTv();
CaptionTv c= new CaptionTv();

 

Tv타입의 참조변수로는 CaptionTv 인스턴스 중에서 Tv클래스 멤버들만 사용할 수 있다.

즉, 참조변수 t는 CaptionTv인스턴스 멤버 중 Tv클래스에 정의되지 않은 멤버(= text와 caption() ) 은 사용이 불가능하다. 

: 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

 


조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.    
ex) Tv t= new CaptionTv();
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.  
ex) CatpionTv c= new Tv(); //불가능 

 


📌 참조변수의 형변환 

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

 

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

위와 같이 세 클래스간의 관계가 있다면,

Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;

car= fe; // 자손타입 -> 조상타입 : 형 변환 생략 가능 
// car = (Car)fe 와 동일
fe2= (FireEngine)car; // 조상타입-> 자손타입 : 형 변환 생략 불가
  • Car 타입의 참조변수 c를 Car타입의 조상인 Object타입의 참조변수로 형변환 하는 것은 참조변수가 다룰 수 있는 멤버의 개수가 실제 인스턴스가 갖고있는 멤버의 개수부터 적을 것이 분명하므로 문제가 되지 않는다. 그래서 형변환을 생략할 수 있도록 한 것이다. 
  • 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 

 

Car car= new Car();
Car car2= null;
FireEngine fe= null;

fe= (FireEngine)car; //컴파일되지만 오류발생.
// FireEngine fe= new Car(); 
//참조변수 car가 참조하고 있는 인스턴스가 Car타입의 인스턴스이므로 
// 조상타입의 인스턴스를 자손 타입의 참조변수로 참조하는 것이므로 허용 불가.

 

서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 
참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다.
그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다. 
> 형변환을 사용하기 전에 instanceof 연산자를 사용해서 참조변수가 참조하고 있는 실제 인스턴스 타입을 확인하는 것이 안전하다. 

 

📌 instanceof연산자

: 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위한 연산자.

 

  • 조건문에 사용되며, 결과로는 boolean값인 true나 false를 반환한다. 
  • if( 참조변수 instanceof 타입 ) 과 같이 사용한다. 
void doWork(Car c){
	if(c instanceof FireEngine){
    	FireEngine fe= (FireEngine)c;
        fe.water();
        ...
    }
}

 

결과값

 

  • 값이 null인 참조변수에 대해 instanceof 연산을 수행하면 false의 결과를 얻는다.
  • 실제 인스턴스와 같은 타입인 경우뿐만 아니라 조상타입의 instanceof 연산에도 true의 결과를 얻는다.
  • instanceof 연산의 결과가 true라는 것은 검사한 타입으로 형변환해도 아무런 문제가 없다는 뜻이다.

 


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

 

조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스 변수를 자손클래스에 중복으로 정의했을 때,

조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우에는 서로 다른 결과를 얻는다. 

  • 메서드의 경우 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드가 호출 된다.
  • 멤버변수의 경우 참조변수의 타입에 따라 달라진다. 
    • 멤버변수가 조상클래스와 자손 클래스에 중복으로 정의된 경우,
    • 조상타입의 참조변수를 사용했을 때는 조상클래스에 선언된 멤버변수가 사용되고
    • 자손타입의 참조변수를 사용했을 때는 자손클래스에 선언된 멤버변수가 사용된다. 

 

결과값 

 

  • 메서드의 경우 동일하게 실제 인스턴스 메서드인 Child의 메서드가 호출되지만,
  • 멤버변수의 경우 실제 참조변수의 타입의 멤버변수를 호출한다. 

하지만 자손클래스와 조상클래스에 동일한 멤버를 중복으로 정의하지 않은 경우에는, 참조변수 타입에 따른 변화는 없다. 

 


📌 매개변수의 다형성

- 메서드의 매개변수로 조상클래스를 사용하면, 해당 클래스의 자손타입 참조변수도 사용할 수 있다. 

 


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

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

Product p[]= new Product[3];
p[0]= new TV();
p[1]= new Computer();
p[2]= new Audio();
  • 조상 타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 사용할 수 있다.