다형성 기초

한라봉봉·2023년 12월 16일

JAVA

목록 보기
13/16

다형성시작

객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있다.
프로그래밍에서 다형성은 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 뜻한다.

1. 다형적 참조

다형적 참조(부모타입의 변수가 자식 인스턴스 참조)하는 코드를 보자.

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

        //Child child1 = new Parent(); //자식은 부모를 담을 수 없다.

        //자식의 기능은 호출할 수 없다. 컴파일 오류 발생
        //poly.childMethod();
  1. 부모는 자식을 담을 수 있다.
    Parent poly = new Child() : 성공
    1) 하위에 손자가 있다면 손자도, 그 하위 타입도 참조할 수있다.
    Parent poly = new Grandson() : Child 하위에 손자가 있다면 가능

  2. 반대로 자식타입은 부모타입을 담을 수 없다.
    Child child1 = new Parent() : 컴파일 오류 발생

  3. 자바에서 부모타입은 자신은 물론이고, 자신을 기준으로 모든 자식타입을 참조할 수있다. 이것이 바로 다양한 형태를 참조할 수 있다고 해서 다형적 참조라 한다.

  4. 다형적 참조와 인스턴스 실행

  • poly.parentMethod()를 호출하면 먼저 참조값으로 인스턴스를 찾는다.
  • 인스턴스 안에서 실행할 타입도 찾아야 하는데, poly는 Parent 타입이다. 따라서 Parent 클래스부터 시작해서 필요한 기능을 찾는다.
  • Parent 클래스에 parentMethod()가 있다. 따라서 해당 메서드가 호출된다.

  1. 다형적 참조의 한계
    Parent poly = new Child(); 이렇게 자식을 참조한 상황에서 ploy가 자식 타입인 Child에 있는 chlidMethod()를 호출하면 어떻게 될까?
  • poly.chlidMethod() 를 실행하면 먼저 참조값x001을 통해 인스턴스를 찾음
  • 인스턴스 안에서 호출자인 poly의 타입인 Parent 클래스부터 시작해서 필요한 기능을 찾음
  • chlidMethod()는 Child 쪽에 있는데, 상속관게는 자식방향으로 찾아 내려갈수는 없으므로 컴파일 오류가 발생
    이런경우 childMethod()를 호출하고 싶으면 캐스팅이 필요하다.
  1. 다형적 참조의 핵심은 부모는 자식을 품을 수 있다는 것이다.

2. 다형성과 캐스팅

    public static void main(String[] args) {
        //부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child(); //x001
        //단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
        //poly.childMethod();

        //다운캐스팅(부모 타입 -> 자식 타입)
        Child child = (Child) poly; //x001
        child.childMethod();
    }
  1. 호출하는 타입을 자식인 Child 타입으로 변경하면 인스턴스의 Chlid에 있는 childMethod()를 호출 할 수 있다.
    (Child)를 사용해서 일시적으로 자식타입인 Child 타입으로 변경한다. (여기서는 참조대상을) 특정 타입으로 변경하는 것을 캐스팅이라 한다.

  2. 참고로 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것은 아니다. 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것이다.(child 변수 저장) poly의 타입은 Parent로 기존과 같이 유지된다.

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

3. 캐스팅 개요

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

4. 캐스팅 종류

  1. 일시적 다운 캐스팅
    일시적 다운 캐스팅을 하면 별도의 변수 없이 인스턴스 자식 타입의 기능을 사용할 수있다.
    (Parent poly의 타입이 변하는 것은 아니다. 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것이다. poly의 타입은 Parent로 기존과 같이 유지된다.)
        //일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
        ((Child) poly).childMethod();
  1. 업캐스팅
    다운 캐스팅과 반대로 현재 타입을 부모 타입으로 변경하는 것
    업캐스팅은 매우 자주 사용하기 때문에 생략을 권장한다. 다운캐스팅은 생략할 수 없다.
    public static void main(String[] args) {
        Child child = new Child();
        Parent parent1 = (Parent) child; //업캐스팅은 생략 가능, 생략 권장
        Parent parent2 = child; //업캐스팅 생략

        parent1.parentMethod();
        parent2.parentMethod();
    }

5. 다운 캐스팅과 주의점

parent2는 new Parent();로 생성되었다. 따라서 참조하면 메모리상에 Child 자체가 존재 하지 않는다. ClassCastException이 그래서 발생한다.

public static void main(String[] args) {
        Parent parent1 = new Child();
        Child child1 = (Child) parent1;
        child1.childMethod(); //문제 없음

        Parent parent2 = new Parent();

        Child child2 = (Child) parent2; //런타임 오류 - ClassCastException
        child2.childMethod(); //실행 불가
    }

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

  1. 업캐스팅의 경우는 이런 문제가 절대로 발생하지 않는다. 왜냐면 객체를 생성하면 해당 타입의 상위 부모 타입은 모두 함께 생성 되기 때문이다.
  2. 반면 다운캐스팅의 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다.(런타임 오류 발생) 개발자는 이런 문제를 인지하고 사용해야 한다는 의미로 명시적으로 캐스팅 해주어야 한다.

7. 컴파일 오류 vs 런타임 오류

  1. 컴파일 오류는 변수명 오타, 잘못된 클래스 이름 사용 등 자바 프로그램을 실행하기 전에 발생하는 오류이다. 이런 오류는 IDE에서 즉시 확인할 수있기 때문에 안전하고 좋은 오류이다.
  2. 반면에 런타임 오류는 이름 그대로 프로그램이 실행하고 있는 시점에 발생하는 오류이다. 런타임 오류는 매우 안좋은 오류이다. 왜냐하면 보통 고객이 해당 프로그램을 실행하는 도중에 발생하기 때문이다.

8. instanceof

  1. 다형성에서 참조형 변수는 이름 그대로 다양한 자식을 대상으로 참조할 수 있다. 어떤 인스턴스를 참조하고 있는 확인하려면 어떻게 해야할까?
    Parent parent1 = new Parent();
    Parent parent2 = new Child();
    parent1, parent2 변수가 참조하는 인스턴스의 타입을 확인하고 싶다면 instanceof 키워드를 사용하면 된다.

  2. 예제를 보자.
    1) call 메서드는 매개변수로 넘어온 parent가 참조하는 인스턴스 타입에 따라서 다른 명령을 수행한다.
    2) 다운캐스팅을 수행하기 전에는 먼저 instanceof를 사용해서 원하는 타입으로 변경이 가능한지 확인한 다음에 다운 캐스팅을 수행하는 것이 안전하다.

public class CastingMain5 {

    public static void main(String[] args) {
        Parent parent1 = new Parent();
        System.out.println("parent1 호출");
        call(parent1);

        Parent parent2 = new Child();
        System.out.println("parent2 호출");
        call(parent2);
    }

    private static void call(Parent parent) {
        parent.parentMethod();
        if (parent instanceof Child) {
            System.out.println("Child 인스턴스 맞음");
            Child child = (Child) parent; // Child 인스턴스가 맞으면 다운캐스팅 해도 됨
            child.childMethod();
        }
    }

}
  1. instanceof 키워드는 왼쪽에 타입 인스턴스가 오른쪽 타입에 들어갈수 있는지 대입해 보면 된다. 대입 가능은 true, 불가능하면 false가 된다.

9. 자바 16 Pattern Matching for instanceof

자바 16 부터는 instanceof를 사용하면서 동시에 변수를 선언 할 수 있다.
인스턴스가 맞는경우, 직접 다운캐스팅 하는 코드를 생략할 수 있다.

    private static void call(Parent parent) {
        parent.parentMethod();
        //Child 인스턴스인 경우 childMethod() 실행
        if (parent instanceof Child child) { // instanceof를 사용하면서 동시에 변수 선언
            System.out.println("Child 인스턴스 맞음");
            //Child child = (Child) parent; 코드 생략됨
            child.childMethod();
        }
    }

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

  1. 다형성을 이루는 또하나의 중요한 핵심 이론은 바로 메서드 오버라이딩이다.
    오버라이딩된 메서드가 항상 우선권을 가진다는 점이다.
public class OverridingMain {

    public static void main(String[] args) {

        //부모 변수가 자식 인스턴스 참조(다형적 참조)
        Parent poly = new Child();
        System.out.println("Parent -> Child");
        System.out.println("value = " + poly.value); //변수는 오버라이딩X 부모의 인스턴스를 참조한다.
        poly.method(); //메서드 오버라이딩됨! Child의 메소드가 나온다
    }
}

  1. 이부분이 중요
    1) poly 변수는 Parent 타입이다. 따라서 poly.value, poly.method()를 호출하면 인스턴스의 Parent 타입에서 기능을 찾아서 실행한다.
    1-1) poly.value: Parent 타입에 있는 value값을 읽는다.
    1-2) poly.method(): Parent 타입에 있는 method()를 실행 하려고 한다. 그런데 하위 타입인 Child.method()가오버라이딩 되어있다. 오버라이딩된 메서드는 항상 우선권을 가진다. 따라서 Parent.method()가 아니라 Child.method()가 실행된다.
  2. 오버라이딩된 메서드는 항상 우선권을 가진다. 자식도, 손자도 오버라이딩하면 더 하위자식의 오버라이딩된 메서드인 손자가 우선권을 가진다.
profile
백엔드 개발공부 로그를 기록합니다

0개의 댓글