Static binding and dynamic binding in Java are described in detail

  • 2020-04-01 03:40:58
  • OfStack

The execution of a Java program is both compiled and interpreted, and Java is an object-oriented programming language. When a subclass and a parent class have the same method, and the subclass overrides the method of the parent class, does the program call the method at run time to call the method of the parent class or the subclass overrides the method? The first thing we'll do here is determine which method implementation or variable is called a binding.

There are two ways of binding in Java, one is static binding, also known as early binding. The other is dynamic binding, also known as late binding.

Difference between contrast

1. Static binding occurs at compile time and dynamic binding at run time
2. Use private or static or final modified variables or methods, using static binding. Virtual methods (methods that can be subclassed) are dynamically bound based on the object at run time.
3. Static binding is done with class information, while dynamic binding is done with object information.
4. Overloaded methods are done using static binding, and overridden methods are done using dynamic binding.

An example of an overloaded method

An example of an overloaded method is shown here.


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");
      }
  }
}

The result of execution is


22:19 $ java TestMain
a String instance in in Caller

In the above code, the call method has two overloaded implementations, one that takes an Object of type Object as an argument, the other that takes an Object of type String as an argument. STR is a String object, and all call methods that receive arguments of type String are called. The binding here is a static binding at compile time based on the parameter type.

validation

Just look at the surface can't prove that it is static binding, using javap compiler can be verified.


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
}

I see this line 18: invokevirtual #6 // Method TestMain$Caller. Call :(Ljava/lang/String;) V does have a static binding that determines that the caller method that takes the String object as an argument is called.

An example of an override method


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");
      }
  }
}

The result of execution is


22:27 $ java TestMain
a String instance in SubCaller

In the above code, the Caller has an implementation of the call method, SubCaller inherits the Caller, and overrides the implementation of the call method. We declare a Caller type variable callerSub, but this variable points to a SubCaller object. As can be seen from the results, it calls the call method implementation of SubCaller, but not the Caller's call method. The reason for this result is that dynamic binding occurs at runtime, during which you need to determine which version of the call method implementation to invoke.

validation

Using javap does not directly validate dynamic binding, and then if it proves to be non-static, then dynamic binding is done.


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
}

As shown above, 18: invokevirtual #6 // Method TestMain$Caller. Call :(Ljava/lang/String;) V here is TestMain$caller.call instead of TestMain$subcaller.call, because the implementation of the calling subclass or the parent class cannot be determined at compile time, so it is left to the dynamic binding at runtime.

Overwrite when overloaded

The following example is a bit odd. There are two overloads of call methods in the Caller class. This is actually a combination of the two.

The following code first has a static binding that determines that the call method with a String object parameter is invoked, and then a dynamic binding at run time that determines whether the call implementation of the executing subclass or the parent class is executed.


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");
      }
  }
}

The execution result is


22:30 $ java TestMain
a String instance in in SubCaller

validation

As described above, I will only post the decompilation results here


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
}

Curious question

Must dynamic binding?

In theory, some method bindings can also be implemented by static bindings. Such as:


public static void main(String[] args) {
      String str = new String();
      final Caller callerSub = new SubCaller();
      callerSub.call(str);
}

For example, here, callerSub holds the subCaller object and the callerSub variable is final, and immediately executes the call method. Theoretically, the compiler can know that the call method of subCaller should be called through enough analysis code.

But why not static binding?
Suppose our Caller inherits from a framework's BaseCaller class, which implements the call method, and BaseCaller inherits from SuperCaller. SuperCaller also implements the call method.

Consider BaseCaller and SuperCaller in a framework 1.0


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");
  }
}

We implemented this using framework 1.0. The Caller inherits from BaseCaller and calls the super.call method.


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");
      }
  }
}

Then we compiled the class file based on the 1.0 version of the framework, assuming that the static binding could determine the implementation of the above Caller's super-call as basecaller. call.

Then we assume again that BaseCaller does not override SuperCaller's call method in the 1.1 version of this framework, and the above assumption that the statically bound call implementation can be problematic in the 1.1 version, because super. Call should be implemented using SuperCall method instead of BaseCaller's call method.

So, some of the things that can actually be bound statically, for reasons of security and consistency, are bound dynamically.

Optimization enlightenment?

Because dynamic binding requires you to determine at runtime which version of the method implementation or variable to execute, it is more time consuming than static binding.

Therefore, without affecting the overall design, we can consider modifying methods or variables with private, static or final.


Related articles: