정적 및 동적 바인딩이 실제로 어떻게 작동 하는지 이해하려면 ? 또는 컴파일러와 JVM에서 어떻게 식별됩니까?
Mammal
메서드 speak()
와 Human
클래스 extends 가있는 부모 클래스가있는 아래 예제를 살펴 보겠습니다 . 메서드를 Mammal
재정의 speak()
한 다음 다시 speak(String language)
.
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
// Valid overload of speak
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
// Code below contains the output and bytecode of the method calls
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
위 코드를 컴파일하고를 사용하여 바이트 코드를 살펴보면 javap -verbose OverridingInternalExample
컴파일러가 프로그램 자체에 추출하여 포함시킨 프로그램의 모든 메서드 호출과 바이트 코드에 정수 코드를 할당하는 상수 테이블을 생성하는 것을 볼 수 있습니다. 모든 메서드 호출 아래의 주석을 참조하십시오)
위의 코드를 살펴보면 우리의 바이트 코드 것을 볼 수 있습니다 humanMammal.speak()
, human.speak()
그리고 human.speak("Hindi")
완전히 다른 ( invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) 컴파일러는 인수 목록 및 클래스 참조를 기반으로 그들을 구별 할 수 있기 때문이다. 이 모든 것이 컴파일 타임에 정적으로 해결되기 때문에 Method Overloading 을 Static Polymorphism 또는 Static Binding이라고 합니다.
그러나 컴파일러에 따라 두 메서드가 모두 참조 로 호출되기 때문에 anyMammal.speak()
및 humanMammal.speak()
에 대한 바이트 코드 는 동일합니다 ( invokevirtual #4
) Mammal
.
이제 두 메서드 호출에 동일한 바이트 코드가 있으면 JVM이 호출 할 메서드를 어떻게 알 수 있습니까?
글쎄, 대답은 바이트 코드 자체에 숨겨져 있으며 invokevirtual
명령어 세트입니다. JVM은 invokevirtual
명령을 사용하여 C ++ 가상 메소드에 해당하는 Java를 호출합니다. C ++에서 다른 클래스의 한 메서드를 재정의하려면 가상으로 선언해야하지만 Java에서는 자식 클래스의 모든 메서드를 재정의 할 수 있기 때문에 기본적으로 모든 메서드가 가상입니다 (개인, 최종 및 정적 메서드 제외).
Java에서 모든 참조 변수에는 두 개의 숨겨진 포인터가 있습니다.
- 객체의 메서드를 다시 보유하는 테이블에 대한 포인터와 Class 객체에 대한 포인터입니다. 예 : [speak (), speak (String) Class 객체]
- 해당 개체의 데이터 (예 : 인스턴스 변수 값)에 대해 힙에 할당 된 메모리에 대한 포인터입니다.
따라서 모든 개체 참조는 해당 개체의 모든 메서드 참조를 보유하는 테이블에 대한 참조를 간접적으로 보유합니다. Java는이 개념을 C ++에서 차용했으며이 테이블을 가상 테이블 (vtable)이라고합니다.
vtable은 배열 인덱스에 대한 가상 메서드 이름과 참조를 보유하는 배열과 같은 배열입니다. JVM은 클래스를 메모리에로드 할 때 클래스 당 하나의 vtable 만 생성합니다.
따라서 JVM이 invokevirtual
명령어 세트를 만날 때마다 해당 클래스의 vtable에서 메서드 참조를 확인하고 우리의 경우 참조가 아닌 객체의 메서드 인 특정 메서드를 호출합니다.
이 모든 것이 런타임에만 해결되고 런타임에 JVM이 호출 할 메서드를 알기 때문에 메서드 재정의 를 Dynamic Polymorphism 또는 단순히 Polymorphism 또는 Dynamic Binding이라고 합니다.
내 기사 에서 JVM이 메서드 오버로딩 및 재정의를 내부적으로 처리하는 방법 에 대한 자세한 내용을 읽을 수 있습니다 .