자바에서 오버라이드(Override)의 원리

jonghyun.log·2023년 4월 27일
1

JAVA

목록 보기
2/2
post-thumbnail

자바에서 오버라이드(Override)란?

  1. 상속관계의 객체들 사이에서 부모타입의 정의된 메서드를 하위타입에서 상속받아 사용할 때 상속받은 메서드를 상위 타입의 메서드와는 다른 방식으로 구현해서 사용
  1. 인터페이스를 구현한 클래스가 인터페이스에 정의된 메서드 명세를 재정의해서 사용

하는 위의 두가지 상황에서 사용되는 자바 문법입니다.

그렇다면 1번과 2번의 예시를 각각 코드를 통해 간단하게 알아보겠습니다.

1번 코드 예시

class SuperClass {
    int x;

    public void f() {
        System.out.println("SuperClass");
    }
}


class SubClass extends SuperClass {

    @Override
    public void f() {
        System.out.println("SubClass");
    }

}

2번 코드 예시

interface C{
    public void f();
}

class D implements C {
    @Override
    public void f() {}
}

이런 코드는 자바로 프로그래밍 해본 사람이라면 수도 없이 봐왔을 것입니다.
상위타입 혹은 인터페이스에 정의된 메서드를 재정의하여 사용하고자 할때 사용하는 문법이 바로 오버라이드(Override) 입니다.

자바에서 오버라이드가 되는 이유

위의 1번 예시 코드를 활용한 다른 예시 코드를 보겠습니다.

public class Test {

    public static void main(String[] args) {
    
        SuperClass superClass = new SuperClass();
        superClass.f();
        
        SubClass subClass = new SubClass();
        subClass.f();
    }
}

출력결과
SuperClass
SubClass

SubClass.f()는 오버라이드에 의해SubClass의 정의된 f()가 호출된 것을 확인해 볼 수 있습니다.
자 그렇다면 이번에는 이렇게 한번 해보면 어떨까요?

public class Test {

    public static void main(String[] args) {
        
        SuperClass superClass = new SubClass();
        superClass.f();
    }
}

출력결과
SubClass

f() 메서드가 오버라이드 된 것은 알겠는데
분명 상위 타입의 참조변수로 하위타입의 인스턴스를 참조한 상태(업캐스팅이라고 부릅니다)에서
메서드를 호출했는데도 어떻게 이게 하위 타입의 오버라이드 된 메서드로 인식하고 호출하는 것일까요?

자바의 동작 과정

이 상황을 이해하려면 .java 파일을 .class 파일로 컴파일 하고 JVM이 실행하는 과정에서 일어나는 일을 알아야 합니다.

정적 바인딩? 동적 바인딩?

갑자기 자바 문법이야기를 하다가 바인딩이 튀어나와서 당황할 수 있는데 이를 이해해야 오버라이드의 원리를 이해할 수 있습니다.

컴파일 타임에 어떤 메서드 호출문을 보고 어떤 메서드를 실행할 것인지를 결정하는 것이
정적 바인딩

런타임 시점에 JVM이 메서드 호출문을 보고 동적(가변적)으로 실행할 메서드를 결정하는 것이
동적 바인딩

이를 가지고 바로 위의 예시를 다시 보면

public class Test {

    public static void main(String[] args) {
        
        SuperClass superClass = new SubClass();
        superClass.f(); // <- 컴파일 시에는 상위타입의 f()로 인식
    }
}

우리는 위 코드를 시간적으로 두가지 측면에서 바라볼 필요가 있습니다.

  1. 컴파일 시점에 컴파일러superClass.f()를 인식하는 방법
  2. 런타임 시점에 JVMsuperClass.f()를 인식하는 방법

1. 컴파일 시점에 컴파일러superClass.f()를 인식하는 방법

우선 컴파일 시점에는 프로그램이 실행된 것이 아니므로 메모리에는 어떤 인스턴스도 올라와 있지 않습니다. 컴파일 시점에는 컴파일러가 참조변수의 타입을 기준으로 메서드 호출을 결정하게 됩니다.

즉, 하위 타입의 객체를 상위타입의 참조변수로 참조하고 있기 때문에 해당함수를 상위타입의 클래스에서 찾아서 해당 메서드가 있는지 확인하고 해당 메서드가 있으므로 문제가 없다고 인식하고 컴파일하게 됩니다.

2. 런타임 시점에 JVMsuperClass.f()를 인식하는 방법

그 다음 런타임 시점이 되면 JVM이 코드를 쭉 읽다가 특정 클래스 파일이 나오면 힙(heap) 공간에 올려서 인스턴스를 생성합니다.

JVM(JAVA Virtual Machine)은 프로그램을 실행할때 한번에 프로그램 실행에 필요한 모든 class 파일을 메모리에 로딩하지 않고 그때 그때 필요한 .class 파일들을 가져와서 로딩하는 방식으로 동작합니다.
가령 main함수를 실행하다가 SuperClass가 나오는 부분을 찾으면 그때 SuperClass.class 파일을 메모리에 로딩하는 식으로 작동하는 것이죠.

런타임 시점에서 JVM

SuperClass superClass = new SubClass();

코드를 확인하게 되면

메모리에는 위 사진과 같은 방식으로 인스턴스가 생성됩니다.
여기서 중요한 점은 하나의 인스턴스 안에 상위 타입의 필드나 메서드들을 할당하기 위한 공간
하위 타입의 필드나 메서드들을 할당하기 위한 공간 이렇게 두가지가 할당이 된다는 점 입니다.

그 다음 JVMsuperClass.f() 코드를 확인하게 되면 우선 SuperClass 타입의 참조변수로 인스턴스를 참조하고 있으니 상위타입의 공간에서 메서드를 찾게 됩니다.
그 이후, 이 인스턴스가 상속구조인것을 파악한 후 하위 타입공간에 정의되어 있는 f()라는 메서드를 찾아서 호출하게 되는 것이지요.

이렇게 런타임 시점에 호출하는 메서드를 동적으로 찾아서 결정하는 것을 동적 바인딩 이라고 합니다.

즉, 상속 구조의 클래스에서 오버라이드 된 메서드들은 런타임 시기에 호출하는 메서드들을 JVM이 결정해주는다는 것이죠.

그리고 이런식으로 동적 바인딩을 진행할때 상위타입에 대한 명시적인 코드가 없으면 예시) super.f() 기본으로 최하위 타입의 메서드를 호출하게 됩니다.

결론

자바에서 오버라이드는 상속관계에서 하위 타입 객체에서 메서드를 다르게 구현해서 사용하고 싶을 때 사용됩니다. 저도 이 내용은 제대로 알지못하고 그냥 사용하기만 했었는데 이번에 자바를 공부하다 보니 원리를 이해하게 되어 정리하는 글을 작성하게 되었네요 ㅎㅎ

0개의 댓글