Java의 정적 바인딩 및 동적 바인딩 상세 정보

하나의 자바 프로그램의 실행은 컴파일과 실행(해석) 두 단계를 거쳐야 하며, 동시에 자바는 대상을 대상으로 하는 프로그래밍 언어이기도 하다.하위 클래스와 상위 클래스가 같은 방법이 존재하면 하위 클래스는 상위 클래스를 다시 쓰는 방법을 사용합니다. 프로그램이 실행될 때 호출하는 방법은 상위 클래스를 호출하는 방법입니까, 아니면 하위 클래스를 다시 쓰는 방법입니까? 이것은 우리가 자바를 처음 배울 때 겪은 문제입니다.여기서 우선 우리는 이런 호출이 어떤 방법으로 실현되거나 변수의 조작을 귀속이라고 하는지 확정할 것이다.
자바에는 두 가지 귀속 방식이 존재하는데 하나는 정적 귀속이고 조기 귀속이라고도 부른다.또 하나는 동적 귀속이고 후기 귀속이라고도 부른다.
구별 대비
1. 정적 귀속은 컴파일 시기, 동적 귀속은 실행 시 발생
2.private 또는static 또는final로 수식된 변수나 방법을 사용하여 정적 귀속을 사용합니다.허법(이불류로 다시 쓸 수 있는 방법)은 실행할 때의 대상에 따라 동적 귀속됩니다.
3. 정적 귀속은 클래스 정보를 사용하여 완성하고, 동적 귀속은 대상 정보를 사용하여 완성해야 한다.
4. 다시 불러오기(Overload) 방법은 정적 귀속을 사용하고, 다시 쓰기(Override) 방법은 동적 귀속을 사용합니다.
재부팅 방법의 예
여기에 재부팅 방법의 예시를 보여 줍니다.

public class TestMain {
  public static void main(String[] args) {
      String str = new String();
      Caller caller = new Caller();
      caller.call(str);
  }

  static class Caller {
      public void call(Object obj) {
          System.out.println("an Object instance in Caller");
      }
     
      public void call(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
}

실행 결과는 다음과 같습니다.

22:19 $ java TestMain
a String instance in in Caller
위의 코드에서call 방법은 두 가지 재부팅 실현이 존재한다. 하나는 Object 형식을 수신하는 대상을 매개 변수로 하고, 다른 하나는 String 형식을 수신하는 대상을 매개 변수로 한다.str는 String 대상입니다. 모든 String 형식의 매개 변수를 수신하는call 방법이 호출됩니다.이곳의 귀속은 컴파일 시기에 매개 변수 유형에 따라 정적 귀속이다.
검증
표상만 봐도 정적 귀속을 증명할 수 없습니다.javap로 컴파일하면 검증할 수 있습니다.

22:19 $ javap -c TestMain
Compiled from "TestMain.java"
public class TestMain {
  public TestMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: invokespecial #3                  // Method java/lang/String."<init>":()V
       7: astore_1
       8: new           #4                  // class TestMain$Caller
      11: dup
      12: invokespecial #5                  // Method TestMain$Caller."<init>":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V
      21: return
}

이 줄을 보았습니다18: invokevirtual #6//Method TestMain$Caller.call:(Ljava/lang/String;)V는 정적 귀속이 발생하여 String 대상을 매개 변수로 수신하는caller 방법을 확정했다.
재작성 방법의 예

public class TestMain {
  public static void main(String[] args) {
      String str = new String();
      Caller caller = new SubCaller();
      caller.call(str);
  }
 
  static class Caller {
      public void call(String str) {
          System.out.println("a String instance in Caller");
      }
  }
 
  static class SubCaller extends Caller {
      @Override
      public void call(String str) {
          System.out.println("a String instance in SubCaller");
      }
  }
}
실행 결과는 다음과 같습니다.

22:27 $ java TestMain
a String instance in SubCaller
위의 코드, Caller 중 하나의call 방법의 실현, SubCaller는 Caller를 계승하고call 방법의 실현을 다시 썼다.우리는 Caller 형식의 변수 caller Sub를 설명했지만, 이 변수가 가리키는 것은 SubCaller의 대상입니다.결과에 의하면 그 호출은 Caller의 콜 방법이 아니라 SubCaller의 콜 방법으로 이루어졌다.이 결과가 발생한 원인은 실행할 때 동적 귀속이 발생했기 때문에 귀속 과정에서 어느 버전의call 방법을 사용해야 하는지 확인해야 한다.
검증
javap를 사용하면 동적 귀속을 직접 검증할 수 없습니다. 그리고 정적 귀속을 하지 않았다는 것을 증명하면 동적 귀속을 한 것입니다.

22:27 $ javap -c TestMain
Compiled from "TestMain.java"
public class TestMain {
  public TestMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: invokespecial #3                  // Method java/lang/String."<init>":()V
       7: astore_1
       8: new           #4                  // class TestMain$SubCaller
      11: dup
      12: invokespecial #5                  // Method TestMain$SubCaller."<init>":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V
      21: return
}

위의 결과와 같이 18: invokevirtual #6//Method TestMain$Caller.call:(Ljava/lang/String;)V. 여기는 TestMain $Caller입니다.TestMain$SubCaller가 아닌 콜입니다.call, 컴파일러에서 하위 클래스를 호출할지 부모 클래스를 호출할지 확인할 수 없기 때문에 실행할 때의 동적 귀속을 잃어버려서 처리할 수 있습니다.
무거운 짐을 만났을 때 다시 쓰기를 만나다
다음 예는 약간 변태적이다. Caller 클래스에call 방법의 두 가지 재부팅이 존재한다. 더욱 복잡한 것은 SubCaller가 Caller를 집적하고 이 두 가지 방법을 다시 쓴 것이다.사실 이런 상황은 위의 두 가지 상황의 복합 상황이다.
다음 코드는 우선 정적 연결이 발생하고 호출 파라미터가 String 대상인call 방법을 확인한 다음에 실행할 때 동적 연결을 해서 하위 클래스를 실행하는지 상위 클래스의call을 실행하는지 확인합니다.

public class TestMain {
  public static void main(String[] args) {
      String str = new String();
      Caller callerSub = new SubCaller();
      callerSub.call(str);
  }
 
  static class Caller {
      public void call(Object obj) {
          System.out.println("an Object instance in Caller");
      }
     
      public void call(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
 
  static class SubCaller extends Caller {
      @Override
      public void call(Object obj) {
          System.out.println("an Object instance in SubCaller");
      }
     
      @Override
      public void call(String str) {
          System.out.println("a String instance in in SubCaller");
      }
  }
}
실행 결과는 다음과 같습니다.

22:30 $ java TestMain
a String instance in in SubCaller
검증
위에서 이미 소개했기 때문에, 여기는 반번역 결과만 붙일 뿐이다

22:30 $ javap -c TestMain
Compiled from "TestMain.java"
public class TestMain {
  public TestMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/String
       3: dup
       4: invokespecial #3                  // Method java/lang/String."<init>":()V
       7: astore_1
       8: new           #4                  // class TestMain$SubCaller
      11: dup
      12: invokespecial #5                  // Method TestMain$SubCaller."<init>":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokevirtual #6                  // Method TestMain$Caller.call:(Ljava/lang/String;)V
      21: return
}

궁금하다
동적 귀속이 아니면 안 됩니까?
사실 이론적으로 어떤 방법의 귀속도 정적 귀속으로 실현될 수 있다.예:

public static void main(String[] args) {
      String str = new String();
      final Caller callerSub = new SubCaller();
      callerSub.call(str);
}
예를 들어 여기에서callerSub는subCaller의 대상을 가지고 있고callerSub 변수는final로 즉시call방법을 실행했다. 컴파일러는 이론적으로 코드를 충분히 분석함으로써 SubCaller를 호출해야 하는call방법을 알 수 있다.
그런데 왜 정적 귀속을 하지 않았습니까?
만약에 우리의 Caller가 특정한 프레임워크의 BaseCaller 클래스에서 계승된다고 가정하면 그는call 방법을 실현했고 BaseCaller는 SuperCaller에서 계승했다.SuperCaller에서 콜 방법도 실현되었다.
프레임 1.0의 BaseCaller 및 SuperCaller 가정하기

static class SuperCaller {
  public void call(Object obj) {
      System.out.println("an Object instance in SuperCaller");
  }
}
 
static class BaseCaller extends SuperCaller {
  public void call(Object obj) {
      System.out.println("an Object instance in BaseCaller");
  }
}
우리는 프레임 1.0을 사용하여 이렇게 실현했다.Caller는 BaseCaller로부터 상속되고 Super를 호출합니다.콜 방법.

public class TestMain {
  public static void main(String[] args) {
      Object obj = new Object();
      SuperCaller callerSub = new SubCaller();
      callerSub.call(obj);
  }
 
  static class Caller extends BaseCaller{
      public void call(Object obj) {
          System.out.println("an Object instance in Caller");
          super.call(obj);
      }
     
      public void call(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
 
  static class SubCaller extends Caller {
      @Override
      public void call(Object obj) {
          System.out.println("an Object instance in SubCaller");
      }
     
      @Override
      public void call(String str) {
          System.out.println("a String instance in in SubCaller");
      }
  }
}
그리고 우리는 이 프레임워크의 1.0 버전을 바탕으로class 파일을 컴파일했습니다. 정적 귀속이 위의 Caller의super를 확정할 수 있다고 가정합니다.BaseCaller로 호출합니다.call 구현.
그리고 우리는 이 프레임워크 1.1 버전에서 BaseCaller가 SuperCaller의call 방법을 다시 쓰지 않는다고 가정하면 위의 가설은 정적으로 귀속될 수 있는call이 1.1 버전에서 실현되는 데 문제가 생길 수 있다. 왜냐하면 1.1 버전에서super이기 때문이다.call은 SuperCaller의call 방법으로 이루어져야 하며, 정적 귀속이 확정된 BaseCaller의call 방법으로 이루어진다고 가정하지 않습니다.
그래서 일부 실제적으로 정적 귀속을 할 수 있고 안전과 일치성을 고려하여 아예 동적 귀속을 했다.
얻은 최적화 시사점?
동적 귀속은 실행할 때 어떤 버전을 실행하는 방법의 실현이나 변수를 결정해야 하기 때문에 정적 귀속보다 시간이 오래 걸린다.
그래서 전체적인 디자인에 영향을 주지 않고 방법이나 변수를private,static 또는final로 수식하는 것을 고려할 수 있다.

좋은 웹페이지 즐겨찾기