[6/8] Java 기본 Summary (w. 인프런 김영한)

차재현·2025년 1월 7일
0

[ Backend Study - Java ]

목록 보기
7/11
post-thumbnail

Section 10. 다형성

Chapter 1. 다형성 시작

객체지향의 특징은 캡슐화, 상속, 다형성이 있다. 이 중에서 “다형성”에 대해 배워보자.
다형성은 객체지향의 꽃이라고 불린다.

  • 다형성이란?
    • 이름 그대로 “다양한 형태”를 뜻한다.
    • 하나의 객체가 여러 타입의 객체로 취급될 수 있는 능력.
  • 예제 코드
    package poly.basic;
    
    public class Parent {
    
        public void parentMethod() {
            System.out.println("Parent.parentMethod");
        }
    }
    package poly.basic;
    
    public class Child extends Parent {
    
        public void childMethod() {
            System.out.println("Child.childMethod");
        }
    }
    package poly.basic;
    
    public class PolyMain {
    
        public static void main(String[] args) {
            //부모 변수가 부모 인스턴스 참조
            System.out.println("Parent -> Parent");
            Parent parent = new Parent();
            parent.parentMethod();
    
            //자식 변수가 자식 인스턴스 참조
            System.out.println("Child -> Child");
            Child child = new Child();
            child.parentMethod();
            child.childMethod();
    
            //부모 변수가 자식 인스턴스 참조(다형적 참조)
            System.out.println("Parent -> Child");
            Parent poly = new Child();
            poly.parentMethod();
    
            //Child child1 = new Parent(); //자식은 부모를 담을 수 없다.
    
            //자식의 기능은 호출할 수 없다. 컴파일 오류 발생
            //poly.childMethod();
    
        }
    }
    • 상속 관계에서, 부모 클래스는 자식 클래스를 품을 수 있다. == 다형적 참조 → 위는 비유를 통한 말이고, 부모 클래스 타입의 변수는 자식 클래스의 인스턴스를 받을 수 있다는 뜻이다
    • 자식 클래스를 new로 생성할 때, 힙 영역에 부모 클래스에 대한 인스턴스도 생성되는것은 이전에 배웠다
      그래서 부모 클래스 타입의 변수가 힙 영역의 자식 클래스 인스턴스에서 자기 자신(부모) 인스턴스를 참조할 수 있다.
    • 그리고 상속 관계의 자식 인스턴스의 변수로 부모 메서드를 호출 할 때 다음 흐름이라는 것을 배웠다
      1. “호출자(변수)”의 타입인 자식 인스턴스 먼저 확인
      2. 자식 인스턴스에 없다면, 부모 인스턴스 확인
      3. 부모 인스턴스에서 부모 메서드 확인 후 실행
    • 하지만 다형적 참조의 결과인 자식 인스턴스를 담은 부모 변수는 자식의 메서드를 호출할 수 없다.
      • 힙 영역의 인스턴스에서 메서드를 찾기 위해 탐색할 때, 자신이 상속 받는 방향으로만 탐색 가능
      • 자신이 상속하는 방향(부모 → 자식)으로는 메서드 탐색 불가

Chapter 2. 다형성과 캐스팅

  • 캐스팅이란?
    • 다른 타입으로 변환하는 것
  • 예제 코드
    package poly.basic;
    
    public class CastingMain1 {
    
        public static void main(String[] args) {
            //부모 변수가 자식 인스턴스 참조(다형적 참조)
            Parent poly = new Child(); //x001
            //단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
            //poly.childMethod();
    
            //다운캐스팅(부모 타입 -> 자식 타입)
            Child child = (Child) poly; //x001이라는 참조값만 복사하여 사용
            child.childMethod();
        }
    }
    • 부모 변수에서 자식 메서드를 호출하고 싶다면, “호출자”의 타입이 자식 클래스가 되어야 한다.
      이때 캐스팅을 통해 자식 클래스로 타입을 변환하면 된다.
    • 위 코드에서도 부모변수 poly 앞에 (Child)를 통해 캐스팅하여 자식 변수 child에 넣었다.
    • 이제 child라는 변수는 자식 타입이기에 자식 메서드인 childMethod를 호출할 수 있다.
    • 이때, 부모 변수인 poly는 Child 클래스로 변하지 않고, 그대로 Parent 타입이다.
      자식 변수인 child에 대입할 때는 poly가 참조하는 참조값만 복사하여 사용한다.
      - poly → Parent 타입
      - child → Child 타입
  • 캐스팅의 종류
    • 다운캐스팅 : 부모 클래스 → 자식 클래스 변환
      • 임시적 다운 캐스팅
        Parent parent = new Child();
        
        ((Child) parent).childMethod();
        • 다운 캐스팅을 통해 잠깐 자식 메서드를 호출하고 싶을 때, 사용 가능.
        • 이때, (Child) parent를 괄호로 감싸주지 않는다면 (Child) parent.childMethod()가 되고, Java가 다운 캐스팅보다 메서드 호출을 높은 우선순위로 인식하여 컴파일 에러가 발생한다.
    • 업캐스팅 : 자식 클래스 → 부모 클래스 변환
      • 다형성 시작 부분에서 “부모는 자식을 품을 수 있다” 라고 했다.
      • 사실은 업캐스팅으로 가능한 것이다.
        Child child = new Child();
        Parent parent = (Parent) child; // 업캐스팅은 생략 권장
        • 위처럼 업캐스팅을 Java가 자동으로 넣어준 것이다.
        • 개발자에게 업캐스팅은 생략하기를 권장한다. 왜냐하면 업캐스팅은 매우 자주 사용되기에
          구지 나타내지 않아도 되기 때문이다.
        • 또한 다운캐스팅에 비해 업캐스팅을 생략해도 되는 이유는 다음 챕터에서 자세히 알아보자.

Chapter 4. 다운캐스팅과 주의점

  • 부모 인스턴스로 만들게되면 힙 영역에서 자식 인스턴스는 생성되지 않음을 주의해야 한다.

    • 업캐스팅 vs 다운캐스팅

      • 위 경우처럼 처음에 new를 통해 자식 인스턴스를 만들게되면
        힙 영역에서 부모 인스턴스가 모두 만들어지기에 업캐스팅하여도 아무 문제 없다.

      • 하지만 위처럼 클래스 C에겐 부모 입장인 클래스 B로 인스턴스를 생성 했다면,
        힙 영역에서 자식인 C 인스턴스는 생성되지 않는다.

  • 이렇게 부모 인스턴스에서 다운 캐스팅을 하고 자식 메서드를 호출하면 “런타임 오류”가 발생한다.

    // 위 그림 참조
    C c = (C) new B(); //강제 다운 캐스팅 
    c.methodC(); //런타임 오류, ClassCastException 발생
    
    // 또는
    
    B b = new B();
    ((C) b).methodC(); //런타임 오류, ClassCastException 발생
  • 다운 캐스팅은 개발자가 강제로 할 수 있기에, 이 점을 주의해야 한다.

Chapter 5. instanceof

  • 위에서 다루었듯이 메서드를 호출하려는 변수가 어떤 인스턴스를 담고 있는지 주의해야한다. 그래서 instanceof를 사용하여 상속관계를 가지고 있는 변수의 인스턴스 타입을 확인할 수 있다.
  • 예시 코드
    package poly.basic;
    
    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.childMethod();
            }
        }
    }
    /* 실행 결과
    parent1 호출
    Parent.parentMethod
    parent2 호출
    Parent.parentMethod
    Child 인스턴스 맞음
    Child.childMethod
    */
    • 위 코드 처럼 "확인할 변수" instancof "확인할 타입" 으로 작성하면 되며, 결과는 Boolean이다.
    • instanceof 추가 예시
      Parent parent1 = new Parent();
      Parent parent2 = new Child();
      
      // 1. 부모인지 검사
      parent1 instanceof Parent //true
      -> Parent p = parent1
      parent2 instanceof Parent //true *
      -> Parent p = parent2
      
      // 2. 자식인지 검사
      parent1 instanceof Child //false
      -> Child c = parent1
      parent2 instanceof Child //true
      -> Child c = parent2
      • 1번의 두번째 코드를 보자.
      • 분명 parent2Child인데 true가 나왔다.
        • 이유는 parent2가 참조하는 Child 인스턴스에는 Parent 인스턴스도 생성되어있기 때문이다.
        • 또는 바로 밑에 적힌 Parent p = parent2 가 가능한지 아닌지를 판단하면 된다.
        • 부모는 자식을 담을 수 있기에 true가 나오는 것이다.
  • Java 16 부터는 instanceof 사용시에 바로 다운 캐스팅할 변수를 선언할 수 있게 되었다.
    • 예시 코드
      package poly.basic;
      
      public class CastingMain6 {
      
          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();
              //Child 인스턴스인 경우 childMethod() 실행
              if (parent instanceof Child child) { // Java 16부터 가능
                  System.out.println("Child 인스턴스 맞음");
                  //Child child = (Child) parent; //다운 캐스팅으로 생략 가능
                  child.childMethod();
              }
          }
      
      }
      • 위 코드처럼 "확인할 변수" instancof "확인할 타입" "다운 캐스팅할 변수명" 으로 바로 변수를 선언할 수 있다.
      • instanceof로 변수의 인스턴스가 확인이 되어 런타임 오류가 발생하지 않는 경우에만
        if문이 실행되기에 추가된 기능이다.

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

  • 핵심 : 다형적 참조 관계에서, 오버라이딩 된 메서드가 항상 우선권을 가진다.
  • 예시 코드
    package poly.overriding;
    
    public class Parent {
    
        public String value = "parent";
    
        public void method() {
            System.out.println("Parent.method");
        }
    }
    package poly.overriding;
    
    public class Child extends Parent {
    
        public String value = "child";
    
        @Override
        public void method() {
            System.out.println("Child.method");
        }
    }
    package poly.overriding;
    
    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(); //메서드 오버라이딩!
        }
    }
    
    /* 실행 결과
    Child -> Child
    value = child
    Child.method
    Parent -> Parent
    value = parent
    Parent.method
    Parent -> Child
    value = parent
    Child.method // 자식 메서드가 실행됨
    */
    • 기존에 배웠던 상속관계에서 메서드 호출 시, “호출자”의 타입에 대해 먼저 찾아보고, 없으면 부모 클래스에 있는지 확인하는 것이었다.

    • 위 코드에서, 부모와 자식 클래스 모두 method() 라는 메서드를 가지고 있다.

    • 이때, 다형적 참조를 통해 부모 변수가 자식 인스턴스를 담고 있다.

      • 부모 변수에서 method()를 호출하면, “호출자”의 타입이 부모니까 부모 클래스를 탐색한다.
      • 부모 클래스에 method()가 있으니 부모 method()가 실행되어야 한다.
      • 하지만 실제로는 자식 method()가 실행되었다.
    • 오버라이딩된 메서드가 존재한다면, 절대적으로 우선권을 가지기 때문이다.

      💡 다형성의 핵심은 2가지다. 이제 다음 Section에서 아래 2가지를 실습해보자.
      1. 다형적 참조
      2. 다형성의 메서드 오버라이딩

profile
Develop what? and why?

0개의 댓글