[Java] 다형성

이병수·2024년 1월 5일
0

Java

목록 보기
15/27
post-thumbnail
post-custom-banner

다형성


다형성이란?

한 타입의 참조 변수를 통해 여러 타입의 객체를 참조할 수 있도록 하는 것

객체지향 프로그래밍의 대표적인 특징으로 캡슐화, 상속, 다형성이 있고
다형성은 객체지향 프로그래밍의 꽃이다.

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

다형성을 이해하기 위해 2가지 핵심 이론을 알아보자.

다형성 참조

public class Parent{
    public void parentMethod() {
        System.out.println("Parent.parentMethod");
    }
}
public class Child extends Parent{
    public void childMethod() {
        System.out.println("Child.childMethod");
    }
}

이렇게 상속 관계가 주어진다고 가정을 해보자.

Parent parent = new Parent();
parent.parentMethod(); // 호출 가능

Child child = new Child();
child.childMethod(); // 호출가능

Parent poly = new Child();
poly.parentMethod(); // 호출가능
poly.childMethod(); // 호출 불가능 -> 컴파일 오류 발생
  • 그림을 통해서 확인해보면, 일단 상속관계에서 자식 클래스 타입의 인스턴스를 생성하게 된다면 메모리 주소 안에 자식 클래스 뿐만 아니라 부모 클래스 정보까지 담을 수 있는 공간이 생긴다고 말했다.

  • new Child()를 통해서 Child 인스턴스를 생성하면 메모리 주소 안에 부모 타입 Parent와 Child타입이 두가지 생성이 되며, 이 인스턴스의 주소를 담는 child변수를 통해 메모리에 접근하여 메서드를 호출한다.

  • 그렇다면 new Parent()를 생성하면 어떻게 될까? (메모리 구조 안에는 무슨 일이 일어날까?)

  • 부모 클래스의 인스턴스를 생성하면 메모리 주소 안에 부모클래스 타입만 생성된다.

  • 부모는 자식에 대한 정보를 모르기 때문에 자식클래스 타입이 생성되지 않는다!

Parent parent = new Child();
parent.parentMethod();

이 코드를 실행하면 어떻게 될까?

  • Parent 타입의 참조변수는 생성된 Child()의 메모리 주소값을 저장한다.

  • 이 메모리에 접근하여 해당 부모클래스의 메서드를 호출한다.

  • 결국에 참조변수 parent가 저장한 메모리 주소값 안에는 부모클래스와 자식 클래스에 대한 정보가 들어있다.

그렇다면 왜

Parent poly = new Child();
poly.childMethod();

이거는 호출이 불가능인지?

  • 간단하게 말하면 호출자인 poly의 타입이 Parent 타입이다.

  • 따라서 Parent 클래스부터 시작하여 필요한 기능을 찾는데, 만약 해당 기능이 없다면 부모 방향으로 올라갈 순 있지만 자식 방향으로 내려갈 수 없기 때문이다.

  • Parent는 부모 타입이고 상위에 부모가 없고 (Object 클래스가 있긴한데).. childMethod()를 찾을 수 없으므로 컴파일 오류가 발생한다.



다형적 참조 : 부모는 자식을 품을 수 있다.

Parent poly는 부모 타입이고 new Child()를 통해 생성된 결과는 Child 타입이다.
따라서 부모 타입은 자식 타입을 담을 수 있으므로 문제가 발생하지 않는다.

다만

Child child = new Parent(); // 컴파일 오류 -> 자식은 부모를 담을 수 없다.

즉, 본인을 기준으로 본인의 자식 클래스 타입들은 다 담을 수 있다.

다양한 형태를 참조할 수 있다고 하여 다형적 참조라고 부른다.


다형성과 캐스팅

위의 문제점을 해결하기 위해서 캐스팅을 이용하면 된다.

캐스팅의 종류에는 2가지가 있다.

  • 업캐스팅(Up-Casting)

  • 다운캐스팅(Down-Casting)

Parent poly = new Child();
Child child = (Child) poly;
child.childMethod();

부모 클래스 -> 자식 클래스 로 변경하는 것을 다운캐스팅이라고한다.

반대로 자식 클래스 -> 부모 클래스 로 변경하는 것을 업캐스팅이라고 한다.

그림으로 쉽게 이야기를 하면 이렇게 된다.

Child child = (Child) poly // 다운캐스팅을 통해 부모타입을 자식 타입으로 변환한 다음 대입 시도
Child child = (Child)x001 // 참조값을 읽은 다음 자식 타입으로 지정
Child child = x001 // 최종 결과
  • (타입)처럼 괄호와 그 사이에 타입을 지정하면 참조 대상을 특정 타입으로 변경할 수 있다.

  • 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것은 아니고 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것이다.



캐스팅의 2가지 유형에서

  1. 업캐스팅 -> 이 경우는 생략을 할 수 있다.
Child child = new Child();
Parent parent1 = (Parent) child; // 업캐스팅 생략, 생략 권장
Parent parent2 = child; // 업캐스팅 생략
  • 위의 코드처럼 업캐스팅 경우에는 자주 사용하기 때문에 생략을 권장한다.
  1. 다운 캐스팅 -> 이 경우에는 생략할 수 없고 개발자가 직접 명시적으로 캐스팅을 해야한다.

    • 다운캐스팅은 잘못하면 심각한 런타임 오류가 발생할 수 있다.




Parent parent1 = new Child();
Child child1 = (Child) parent1;
child1.childMethod(); // 문제가 없다
  • 위의 코드 같은 경우에는 문제가 없다

  • 결국 new 연산자를 통해 생성된 인스턴스는 Child 타입이므로 해당 메모리 주소 안에 Child와 Parent 둘다 생성이 되기 때문이다.

  • 처음에 Parent 참조변수에 주소값을 저장했다가 Child 타입으로 주소를 다시 참조하여도 해당 메모리 주소 안에 Child 타입이 있기 때문에 childMethod() 메서드를 호출하여도 문제가 발생하지 않는다.


다만!

Parent parent1 = new Parent();
Child child1 = (Child)parent1;
child1.childMethod(); // 호출 문제 발생
  • 위의 코드에는 문제가 발생한다.

  • new 연산자를 통해 생성된 인스턴스는 Parent 타입이기에 Child 클래스에 대한 정보가 저장되지 않는다.

  • 따라서 다운캐스팅을 하는데에는 문제가 없지만, 자식 클래스 호출자를 통해 해당 메서드를 불러올 떄, Child 인스턴스가 없으므로 런타임 오류가 발생한다.

자바는 이렇게 사용할 수 없는 타입으로 다운캐스팅하는 경우에 ClassCastException 이라는 예외를 발생시킨다.


번외 컴파일 오류 vs 런타임 오류

컴파일 오류는 변수명 오타, 잘못된 클래스 이름 사용 등 자바 프로그램을 실행하기 전에 발생하는 오류로 IDE 에서 즉시 확인할 수 있끼에 좋은 오류이다.

런타임 오류는 이름 그대로 프로그램이 실행되고 있는 시점에서 발생하는 오류로 프로그램 실행 도중에 발생하기에 안좋은 오류이다.


instanceof

참조하는 대상이 다양하기 때문에 인스턴스 타입을 확인하고 싶을 때 사용하는 키워드

  • (대상객체) instance of (클래스이름) 형태로 사용한다.
Parent parent = new Child();

if(parent instanceof Child) {
	System.out.println("true");
}

parent 변수가 참조하는 인스턴스 타입이 Child라면 "true"를 출력하는 코드이다.

이럴 경우 true가 출력된다.

Child child = new Child();

child instanceof Child // -> true 
child instanceof Parent // -> true

자식 클래스 같은 경우에는 부모 클래스까지 포함하고 있기 때문에 true이다.


다형성과 메서드 오버라이딩

오버라이딩 된 메서드가 항상 우선권을 갖는다.

  • 다형성을 이루는 또 하나의 중요한 핵심이론이 바로 메서드 오버라이딩

  • 기존 기능을 덮어 새로운 기능을 재정의한다는 것이다.

public class Parent{
    public String value = "parent";
    
    public void method() {
        System.out.println("Parent.method");
    }
}
public class Child{
    public String value = "child";
    
    @Override
    public void method() {
        System.out.println("Child.method");
    }
}

이럴 경우에

Parent poly = new Child();
System.out.println("value= " + poly.value);  // 변수는 오버라이딩 X
poly.method();			// 메서드 오버라이딩

출력이

value= parent
Child.method

이렇게 출력이 나온다.

  • poly 변수는 Parent 타입이다.

  • 따라서 poly.value, poly.method()를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 확인한다.

  • 변수는 기능 그대로 Parent 타입에 있는 value를 찾는다.

  • 하지만!! 메서드는 위에서 말했듯 오버라이딩 된 메서드가 항상 우선권을 갖는다 이므로 Child.method() 가 오버라이딩 되어 있기 때문에 Child.method()가 호출된다.



다형성은 객체지향에서 꽃이기 때문에 더 활용을 하면 어려워질 수 있으므로 꼭 잘 이해하자.


참고

profile
백엔드 개발자가 되고 싶어요
post-custom-banner

0개의 댓글