[JAVA]김영한의 실전 자바 ; 다형성1

선뀰·2024년 1월 6일
0

JAVA

목록 보기
17/25

다형성

"다양한 형태", "여러 형태"를 뜻한다.
한 객체가 여러 타입의 객체로 취급될 수 있는 능력이다. 다형성을 사용하면 하나의 객체가 다른 타입으로 사용될 수 있다는 뜻이다.

다형적 참조

부모 타입의 변수가 자식 인스턴스를 참조한다.

 //부모 변수가 자식 인스턴스를 참조 (다형적 참조)
        System.out.println("Parent -> Child");
        Parent poly = new Child();
        poly.parentMethod(); // poly.parentMethod를 호출할 수 있다.

Child 인스턴스를 만들었다. 자식 타입인 Child를 생성했기 때문에 메모리 상에 Child와 Parent가 모두 생성된다. 생성된 참조값을 Parent 타입의 변수인 poly에 담아둔다.

- 부모는 자식을 담을 수 있다.
부모 타입은 자식 타입을 담을 수 있다.
Parent poly는 부모 타입이다. new Child()를 통해 생성된 결과는 Child 타입이다.
자바에서 부모 타입은 자식 타입을 담을 수 있다. 하위 손자까지 가능하다.

Parent poly = new Parent()
Parent poly = new Child()
Parent poly = new Grandson()

- 자식 타입은 부모 타입을 담을 수 없다.
Child child1 = new Parent() : 컴파일 오류가 발생한다.
poly.childMethod(); 자식의 기능은 호출할 수 없다. 컴파일 오류가 발생한다.
참조값을 통해 인스턴스를 찾는다. 다음으로 인스턴스 안에서 실행할 타입을 찾아야 한다.
poly는 Parent 타입이다. Parent클래스부터 시작해서 필요한 기능을 찾는다.
상속 관계는 부모 방향으로 찾아 올라갈 수는 있지만 자식 방향으로 찾아 갈 수 없다.
Parent는 부모 타입이고 상위에 부모가 없다.
childMethod()를 찾을 수 없으므로 컴파일 오류가 발생한다.

childMethod()를 호출하려면 캐스팅이 필요하다.

  • 다형적 참조와 인스턴스 실행
    poly.parentMethod() 를 호출하면 참조값을 사용해서 인스턴스를 찾는다.
    부모는 자식을 품을 수 있다.

캐스팅

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

다운캐스팅 (부모 타입 -> 자식 타입)

자식은 부모를 담을 수 없다. 그렇기 때문에 다운캐스팅이라는 기능을 사용하여 부모 타입을 잠깐 자식 타입으로 변경하면 된다. 하지만 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것은 아니다. 참조값이 Child 타입이 되는 것이다. poly의 타입은 Parent로 기존과 같이 유지된다.

// Parent 타입을 강제로 child로 바꿔서 호출이 가능하다.
Child child = (child)poly;
child.childMethod();

캐스팅의 종류

자식 타입의 기능을 사용하려면, 다운캐스팅 결과를 변수에 담아두고 이후에 기능을 사용하면 된다.

다운캐스팅 결과를 변수에 담아두는 과정이 번거롭다. 이런 과정 없이 일시적으로 다운캐스팅을 해서 인스턴스에 있는 하위 클래스의 기능을 바로 호출할 수 있다.

일시적 다운캐스팅

((Child) poly).childMethod();
해당 메서드를 호출하는 순간만 다운캐스팅을 한다. 연산자 우선순위가 애매할 땐 괄호를 사용한다.

별도의 변수 없이 인스턴스의 자식 타입의 기능을 사용할 수 있다.

업캐스팅

Child 타입을 Parent 타입에 대입해야 한다. 부모 타입으로 변환하는 경우 다음과 같이 캐스팅 코드(타입)을 생략할 수 있다.
업캐스팅은 생략이 가능하고, 다운캐스팅은 생략이 불가능하다.

다운캐스팅과 반대로 현재 타입을 부모 타입으로 변경하는 것이다.

Child child = new Child();
Parent parent1 = (Parent) child;
Parent parent2 = child; // 업캐스팅은 생략이 가능하다.

다운캐스팅과 주의점

Parent parent2 = new Parent();
Child CHild2 = (Child) parent2; // 런타임 오류 - ClassCast Exception
child2.childMethod(); // 실행 불가

parent2는 Parent로 생성이 되었다. 메모리 상에 Child 자체가 존재하지 않기 때문에 Child 자체를 사용할 수 없다. 사용할 수 없는 타입으로 다운캐스팅 하는 경우에 ClassCastException이라는 예외가 발생한다. 동작이 실행되지 않고, 프로그램이 종료된다. child2.childMethod()코드 자체가 실행되지 않는다.

업캐스팅이 안전하고 다운캐스팅이 위험한 이유

업캐스팅의 경우 객체를 생성하면 해당 타입의 상위 부모 타입은 모두 함께 생성된다. 본인 위로 다 만들어져 있기 때문에 그리고 타입을 변경하는 업캐스팅은 메모리 상에 인스턴스가 모두 존재하기 때문에 안전한다. 따라서 캐스팅 생략이 가능하다.

다운캐스팅의 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다. 부모 타입은 모두 생성되지만 자식 타입은 생성되지 않는다.
A a = new B() : A로 업캐스팅
B b = new B() : B로 업캐스팅
C c = new B() : 자신과 같은 타입
C c = (C) new B() : 하위 타입으로 강제 다운캐스팅, 하지만 B 인스턴스에 C와 관련된 부분이 없으므로 잘못된 캐스팅이다.
ClassCastException: 런타임 오류 발생이다.

  • 컴파일 오류 vs 런타임 오류
    컴파일 오류 : IDE에서 즉시 확인이 가능하다.

런타임 오류 : 프로그램이 실행 되다가 터져버린다. 고객이 해당 프로그램을 실행하는 도중에 발생한다.

instanceof

다형성에서 참조형 변수는 이름 그대로 다양한 자식을 대상으로 참조할 수 있다. -> 다양하기 때문에 어떤 인스턴스를 참조하고 있는지 확인하려면

Parent parent1 = new Parent()
Parent parent2 = new Child()
parent1, parent2 변수가 참조하는 인스턴스의 타입을 확인하고 싶다면 instanceof 키워드를 사용하면 된다.

  • 메서드를 처음 호출할 때 parent는 Parent의 인스턴스를 참조한다.
    call(Parent parent) : 매개변수로 넘어온 parent가 참조하는 타입에 따라서 다른 명령을 수행
    다운캐스팅을 수행하기 전에는 instanceof를 사용해서 원하는 타입으로 변경이 가능한지 확인한 다음에 다운캐스팅을 수행하는 것이 안전하다.

parent instanceof Child // parent는 Child의 인스턴스
new Child() instanceof Child // true가 된다.
parent는 child의 인스턴스를 참조하므로 true를 반환한다.

  • instanceof키워드는 오른쪽 대상의 자식 타입을 왼쪽에서 참조하는 경우에도 true를 반환
    parent instanceof Parent

new Parent() instanceof Parent //true
new Child() instanceof Parent //true

Pattern Matching for instanceof

instanceof를 사용하면서 동시에 변수를 선언 가능하다.

private static void call(Parent parent) {
        parent.parentMethod(); // parent method가 호출이 된다.
        // Child 인스턴스인 경우 childMethod() 실행한다.
        if (parent instanceof Child child) {
            System.out.println("Child 인스턴스 맞음");
            //Child child = (Child) parent; // 다운캐스팅을 하더라도 문제가 되지 않는다.
            child.childMethod(); //
        } else {
            System.out.println("Child 인스턴스 아님");
        }
    }

parent instanceof Child child로 선언이 가능하기 때문에
Child child = (Child) parent;를 사용하지 않아도 된다. -> 다운캐스팅을 보장하기 때문에 문제가 되지 않는다.

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

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

Parent, Child 모두 value라는 같은 멤버 변수를 가지고 있다.
멤버 변수는 오버라이딩 되지 않는다.
Parent, Child 모두 method()라는 같은 메서드를 가지고 있다. Child에서 메서드를 오버라이딩 함 -> 메서드는 오버라이딩 된다.

  • 오버라이딩 단축키 : ctrl + o
// 부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child();
        System.out.println("Parent -> Child");
        System.out.println("value = " + poly.value); // 변수는 오버라이딩 x
        poly.method(); // 메서드는 오버라이딩이 된다.

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

poly.value : Parent 타입에 있는 value값을 읽는다.
poly.method() : Parent 타입에 있는 method()를 실행하려고 한다. 하위 타입인 Child.method()가 오버라이딩 되어있다. 오버라이딩 된 메서드는 항상 우선권을 가진다.
따라서 Parent.method()가 아니라 Child.method()가 실행된다.
= 인스턴스 안에 있어야 한다. -> 자식이 오버라이딩 경우는 우선권을가짐!!
하위타입에서 새로운 기능으로 재정의하면서 절대적 우선권을 가진다.

profile
공부 기록

0개의 댓글