본문 바로가기

Java/Basic

[Java] 다형성(polymorphism)

다형성이란?

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

- 조상 클래스의 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있도록 하였다는 것이다.

 

클래스 Tv와 클래스 CaptionTv는 서로 상속관계에 있으며, 이 두 클래스의 인스턴스를 생성하고 사용하기 위해서는 다음과 같이 할 수 있다.

- 생성된 인스턴스를 다루기 위해서, 인스턴스의 타입과 일치하는 타입의 참조변수만을 사용했다. Tv인스턴스를 다루기 위해서는 Tv의 타입의 참조변술르 사용하고, CaptionTv 인스턴스를 다루기 위해서는 CaptionTv타입의 참조변수를 사용했다.

- 보통 인스턴스의 타입과 참조변수의 타입이 일치하는 것이 보통이지만, Tv와 CaptionTv클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.

 

 

- 위의 코드에서 CaptionTv인스턴스 2개를 생성하고, 참조변수 c와 t가 생성된 인스턴스를 하나씩 참조하도록 하였다.

- 이 경우 실제 인스턴스가 CaptionTv타입이라 할지라도, 참조변수 t로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다.

- Tv타입의 참조변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버포함)만 사용할 수 있다. 따라서, 생성된 CaptionTv인스턴스의 멤버 중에서 Tv클래스에 정의되지 않은 멤버, text와 caption()은 참조변수 t로 사용이 불가능하다. 즉, t.text 또는 t.caption()와 같이 할 수 없다.

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

 

 

 - 반대로 자손 타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 가능하지 않다. 위의 코드를 그대로 컴파일하면 에러가 발생한다.

   그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버개수가 더 많기 때문이다. 그래서 이를 허용하지 않는다.

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

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


참조변수의 형변환

- 기본형 변수와 같이 참조변수도 형변환이 가능하다.

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

- 기본형 변수의 형변환에서 작은 자료형에서 큰 자료형의 형변환은 생략이 가능하듯이, 참조형 변수의 형변환에서는 자손타입의 참조변수를 조상타입으로 형변환하는 경우에는 형변환을 생략할 수 있다.

example

- 참조변수 car와 fe의 타입이 서로 다르기 때문에, 대입연산(=)이 수행되기 전에 형변환을 수행하여 두 변수간의 타입을 맞춰주어야 한다.

- 그러나, 자손타입의 참조변수를 조사타입의 참조변수에 할당할 경우 형변환을 생략할 수 있어서 'car = fe;'와 같이 하였다. 원칙적으로는 'car=(Car)fe;'와 같이 해야 한다.

- 반대로 조상타입의 참조변수를 자손타입의 참조변수에 저장할 경우 형변환을 생략할 수 없으므로, 'fe2 = (FireEngine)car;'와 같이 명시적으로 형변환을 해주어야 한다. 

 

- 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 반환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다.

  단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절하는 것 뿐이다.

 


instanceof 연산자

- 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다.

- 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치힌다.

- 연산의 결과로 boolean값인 true와 false중의 하나를 반환한다.

- instanceof를 이용한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

- 값이 null인 참조변수에 대해 instanceof연산을 수행하면 false를 결과로 얻는다.

- 위 코드는 Car타입의 참조변수 c를 매개변수로 하는 메서드이다.

- 이 메서드가 호출될 때, 매개변수로 Car클래스 또는 그 자손 클래스의 인스턴스를 넘겨받겠지만 메서드 내에서는 정확히 어떤 인스턴스인지 알 길이 없다.  그래서 instanceof 연산자를 이용해서 참조변수가 가리키고 있는 인스턴스의 타입을 체크하고, 적절히 형변환한 다음에 작업을 해야한다.

- 조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 떄문에, 실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.


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

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

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

 

+ static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스메서드 뿐이다. 그래서 static메서드는 참조변수가 아닌 '클래스이름.메서드()'로 호출해야 한다.

 

example

- 참조변수 p와 c 모두 Childe인스턴스를 참조하고 있다. Parent 클래스와 Child 클래스는 서로 같은 멤버들을 정의하고 있다.

- 메서드인 method()의 경우 참조변수의 타입에 관계 없이 항상 실제 인스턴스의 타입인 Child 클래스에 정의된 메서드가 호출되지만, 인스턴스 변수인 x는 참조변수의 타입에 따라서 달라진다.

 

example

 

 

- 멤버변수들은 주로 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있도록 하지, 위 코드처럼 다른 외부 클래스에서 참조변수를 통해 직접적으로 인스턴스변수에 접근할 수 있게 하지 않는다.

- 인스턴스변수에 직접 접근하면, 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의해아 한다.