Java - 상속과 다형성 깊게 이해하기 (binding)

 

 

상속과 다형성을 처음 접한다면 먼저 그것에 대해 배운 뒤 읽는 것을 추천드립니다.

 

오늘 다룰 주제는 상속과 다형성이다.

 

기초가 되는 개념이지만 파고 들어 보면 생각만큼 간단하지는 않다.

 

그 중 다소 까다로울 수 있는 binding과 상속이 되는 방식에 대해 다루고자 한다.

 

아래 코드를 보자.

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        Parent paild = child;
        System.out.println(parent + " # " + parent.staticMethod());
        System.out.println(child + " # " + child.staticMethod());
        System.out.println(paild + " # " + paild.staticMethod());
        System.out.println(((Parent)child) + " # "
                + ((Parent)child).staticMethod());
        System.out.println(((Child)paild) + " # "
                + ((Child)paild).staticMethod());
    }
}

class Parent {
    public String publicMethod() {
        return "P@public";
    }
    protected String protectedMethod() {
        return "P@protected";
    }
    String defaultMethod() {
        return "P@default";
    }
    private String privateMethod() {
        return "P@private";
    }
    static String staticMethod() {
        return "P@static";
    }
    public String toString() {
        return publicMethod() + " " + protectedMethod() + " "
                + defaultMethod() + " " + privateMethod() + " " + staticMethod();
    }
}

class Child extends Parent {
    public String publicMethod() {
        return "C@public";
    }
    protected String protectedMethod() {
        return "C@protected";
    }
    String defaultMethod() {
        return "C@default";
    }private String privateMethod() {
        return "C@private";
    }
    static String staticMethod() {
        return "C@static";
    }
/*
public String toString() {
return publicMethod() + " " + protectedMethod() + " "
+ defaultMethod() + " " + privateMethod() + " " + staticMethod();
}
*/

}

상황이 복잡해서 직접 보고 이해하는 것을 강력 추천하지만

 

코드를 간단하게 설명하면 

 

부모와 자식 관계 의 두 클래스가 있다. 그리고 둘 모두 이름이 같은 public, protected, default, private, static 메소드를 각각 가지고 있다. 

둘은 상속관계이기 때문에 public, protected의 경우 override하고 있고

private, static의 경우 hide하고 있다고 할 수 있다.

그리고 toString 메소드에서 위 모든 메소드를 호출하고 있다.

 

이 상황에서 2가지 실험을 해보고자 한다.

 

1. 첫 번째 상황은 자식에서 toString 함수를 부모에게 상속받고 있는 상황이다. (주석 처리를 그대로 둔 상황)

2. 두 번째 상황은 자식이 toString 함수를 override하고 있는 상황이다. (주석 처리를 뺀 상황)

이때 toString 메소드를 실행시킨다면 각각 어느 곳에 있는 메소드가 실행될까? 부모? 자식?

 

부모, 자식, Paild, (부모)자식, (자식)Paild로 나눠 생각해보도록 하자.

 

우선 정답은 다음과 같다.(Java에 익숙한 분이라면 한번 정답을 생각해보고 넘어가도 좋을 것 같다.)

 

 

 

 

1번 상황 (부모, 자식, paild, (부모)자식, (자식)paild 순)
2번 상황(위와 동일)

 1. binding에 대해서 - static 메소드 실행

 

# 뒤에 있는 것은 toString과 무관하게 static 메소드만 따로 실행해준 것이다.

결과값을 보면 static 메소드는 explicit type에 따라 실행된다.

그 이유는 static 의 경우 static binding 되기 때문이다.

 

 binding에는 2가지 종류가 있다.

 

  1. static binding(early binding)

    - compile time에 일어난다.
    - final, static, private modifier가 붙은 메소드를 처리할 때 적용된다.
    - 실행 속도가 빠르다.
    - overloading에도 해당된다.


  2. dynamic binding(late binding)

    - run time에 일어난다.
    - static binding에 해당되는 메소드가 아닌 경우 적용된다. (public 등)
    - 실행 속도가 비교적 느리다.
    - override에도 해당된다.

 

static binding이 일어나는 이유는 간단하다.

compiler가 이미 override가 불가능하다고 판단한 메소드들에 대해 더 나은 성능을 위해 compile time에 처리하기 때문이다. 예를 들어 final, private, static의 경우 override가 애초에 불가능하기에 한번에 compile time 때 처리해주는 편이 성능에 더 좋을 것이다.

 

그럼 각각 binding에 따른 실제 실행은 어떻게 될까?

override의 경우 런타임 시에 해당 메소드를 구현한 실체 객체를 찾아가서 호출을 한다. 이것이 흔히 말하는 다형성이다.

 

하지만 static 메소드의 경우 컴파일 시에 생성되고 메모리에 적재된다. 따라서 컴파일 시에 선언된 explicit type(틀) 기준으로 실행된다.

 

 

2. Java에서의 상속 방식 - 재귀 탐색

 

까다로운 것은 상속된 메소드에서 실행되는 각각의 메소드들이다. (toString 메소드에서 각각의 메소드를 실행하는 일)

 

특이한 점은 자식 클래스 입장에서

1. 상속받은 함수에서 실행한 public, protected, default의 경우 자신의 것(자식)이 실행되지만 static, private의 경우 부모의 것이 실행된다.

2. 반대로 상속하지 않고 override한다면 모두 자식의 것이 실행된다.

 

그 이유는

상속은 실제 코드를 copy해서 자식 클래스에 넣는 일이 아니기 때문이다.

reference를 유지하여 관계를 맺는 일에 가깝다.

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial

에 나와있는 것처럼

자바는 호출할 메소드를 재귀적으로 찾아나서고 첫번째 메소드로 match되는 것을 실행한다.

 

상속된 메소드의 경우 실제 코드는 부모에 있기 때문에 다형성에 해당하지 않는 static, private의 경우 compile time에 미리 적재가 되어(위에서 설명한 static-binding 적용) 부모 것이 실행되고 다형성이 적용되는 경우 rum time에 적재되어 자식의 것이 실행될 수 있는 것이다. (1번의 binding과 같이 이해하면 쉽다.)

 

그리고 override를 한다면 자식 메소드에서 모든 실행이 이루어지기 때문에 자식의 것만이 실행된다.

 

 

3. 끝으로

 

위 코드의 경우 다형성에 대해 이해를 잘 하지 못하고 사용할 수 있는 독성 코드라고 할 수 있을 것 같다. 실제로 저런 코드를 짜는 것은 당연히 좋지 않다. 다만 binding과 상속 메소드가 실제로 어떻게 실행되는지 이해하기엔 최적의 코드라 생각하기 때문에 골치 아파도 꼭 짚고 넘어가길 추천한다.

 

 

 

 

 

'programming language > Java' 카테고리의 다른 글

Java - String 깊게 이해해보기  (2) 2022.07.17
Java의 method 실행 방식 - static  (2) 2022.07.17