코딩조각

[Java] 다형성에 대해 알아보자(feat.다형적 참조, 메서드 오버라이딩)

밍튜 2024. 2. 22. 11:06

객체지향 프로그래밍의 대표적 특징들은 다들 알고있을것이다.

 

추상화, 캡슐화, 상속, 다형성...

그중에 가장 중요하고 이해하기 어렵다는 다형성에대해 알아보자.

 

다형성은 Polymorphism이라는 이름 그대로 '다양한 형태', '여러 형태'를 말한다.

프로그래밍에서 다형성은 한 객체가 여러 타입의 객체로 취급될수 있는 능력을 말한다.

즉 타입이 결정되어있는 하나의 객체가 다른 타입으로 사용 될수 있다는 뜻이다...

 

아직 무슨말인지 정확히 모르겠으니 자세히 알아보자

 


 

다형성을 이해하기 위해선 2가지 핵심 이론을 알아야 한다.

1. 다형성 참조

2. 메서드 오버라이딩

 

다형성 참조란 간단히 말해서 "부모는 자식을 담을수 있지만 자식은 부모를 담을수 없다" 라고 할수있다.

 

다음 그림을 보자

 

 

Parent를 상속하는 Child 클래스가 있다고 해보자.

Child 타입의 인스턴스를 생성하면 오른쪽 그림처럼 메모리에 Child와 상속받는 Parent가 모두 생성된다.

위 그림은 Child child1 = new Child();로 선언했을때 그림이다.

 

 

다음 그림은 Parent 타입의 클래스를 인스턴스화 했을때 메모리 그림이다.

 

 

다형성을 이용해 new Child() 인스턴스를 Parent타입의 poly 변수가 참조하고 있는 그림이다.

 

여기까지 다형성을 설명해보면

  • 부모 타입의 변수가 자식 인스턴스를 참조한다
  • 부모타입은 자식을 담을수 있다.
  • Parent poly는 부모타입이고 new Child()를 통해 생성된 결과는 Child 타입이므로
    • Parent poly = new Child()는 가능하다
    • Child child1 = new parent() 는 불가능! 컴파일 오류 발생함

 

하지만 다형적 참조에는 한계가 있다.

위 그림에서는 Child인스턴스를 Parent 타입의 poly변수가 참조하고있다.(다형적 참조)

poly는 Parent타입 변수이므로 자식타입의 Child의 childMethod는 호출이 불가능 하다.

 

그럼이때 '다운 캐스팅'을 해버린다면?

Child child = (Child) poly
child.childMethod()

로 자식타입의 childMethod를 호출하는게 가능하다.

 

  • 업캐스팅(upcasting): 부모 타입으로 변경
  • 다운캐스팅(downcasting): 자식 타입으로 변경

실행 순서

Child child = (Child) poly //다운캐스팅을 통해 부모타입을 자식 타입으로 변환한 다음에 대입 시도 
Child child = (Child) x001 //참조값을 읽은 다음 자식 타입으로 지정 
Child child = x001 //최종 결과

 

참고로 캐스팅을 한다고 해서 Parent poly 의 타입이 변하는 것은 아니고 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 된다. 따라서 poly 의 타입은 Parent 로 기존과 같이 유지된다.

 

 

메서드 오버라이딩이란 다 아는것처럼 상속받은 메서드를 재정의해서 기존 기능을 덮어 새로운 기능으로 한다는 뜻이다.

 

다음 코드를 보자

 

public class OverridingMain {
 	public static void main(String[] args) {
 
 	//자식 변수가 자식 인스턴스 참조
 	Child child = new Child();
 	System.out.println("Child -> Child");
 	System.out.println("value = " + child.value);
 	child.method();
 	
   	 //부모 변수가 부모 인스턴스 참조
 	Parent parent = new Parent();
 	System.out.println("Parent -> Parent");
 	System.out.println("value = " + parent.value);
 	parent.method();
 
 	//부모 변수가 자식 인스턴스 참조(다형적 참조)
 	Parent poly = new Child();
 	System.out.println("Parent -> Child");
 	System.out.println("value = " + poly.value); //변수는 오버라이딩X
 	poly.method(); //메서드 오버라이딩!
  }
}

 

그림을 통해서 코드를 분석해보면

 

                    Parent -> Parent

 

이 부분이 중요하다!

poly 변수는 Parent 타입이다. 따라서 poly.value, poly.method() 를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 실행한다.

'오버라이딩 된 메서드는 항상 우선권을 가진다' <- 이 부분이 핵심!

따라서 Parent.method() 가 아니라 Child.method() 가 실행된다.