Why can't static methods be rewritten in Java? Why can't static methods hide instance methods?

Problem Description

  Why can't static methods be rewritten in Java? Why can't static methods hide instance methods? And so on.

Preliminary preparation

  First understand the meaning of rewriting. Rewriting is the redefining function of the instance method of the parent class in the subclass, and the return type, method name, and parameter list are consistent, and the call to the rewriting method mainly depends on the actual type. If the actual type implements this method, call this method directly. If it is not implemented, search for implementation from low to high in the inheritance relationship. Then the question comes again, why can only be overridden for instance methods? I'm so dizzy, these two issues shirk each other's responsibility here.
  Understand three concepts: static type, actual type, method receiver.

Person student= new Student();
student.work();

The static type is the type that the compiler thinks the object belongs to during compilation. This is mainly determined by the declared type, so the above-mentioned Person is the static type. The
actual type is determined by the interpreter based on the object actually pointed to by the reference during execution, so the Student is the actual type.
The method receiver is the object that is found to execute this method by dynamic binding, such as student.

Also understand the method call instructions of the bytecode in the compiled class file.
(1) invokestatic: call static method
(2) invokespecial: call instance constructor method, private method.
(3) Invokevirtual: call all virtual methods.
(4) Invokeinterface: Invoke the interface method, and an object that implements this interface will be determined at runtime.
(5) Invokedynamic: first dynamically resolve the method referenced by the call point qualifier at runtime, and then execute the method.

Non-virtual methods: methods that cannot be overridden or overridden, refer to construction methods, static methods, private methods, and final modified methods.
Virtual methods: methods that can be overridden, generally referring to instance methods.

example

package com.learn.pra06;
class Demo01{
    public void method1(){
        System.out.println("This is father non-static");
    }
    public static void method2(){
        System.out.println("This is father static");
    }
}
public class Demo02 extends Demo01{
    public void method1(){
        System.out.println("This is son non-static");
    }
    public static void method2(){
        System.out.println("This is son static");
    }

    public static void main(String[] args){
        Demo01 d1= new Demo01();
        Demo02 d2= new Demo02();
        Demo01 d3= new Demo02(); //父类引用指向子类对象
        d1.method1();
        d1.method2();
        d2.method1();
        d2.method2();
        d3.method1();
        d3.method2();
    }
}

Operation result:
Write picture description here
  There should be no doubt about the first 5 lines of such operation result, which can be understood with conventional thinking, but the last one, what? What about dynamic binding, Are you kidding me? No, dynamic binding does not happen here, and the question is coming again, why doesn't dynamic binding happen to static methods? What happened to dynamic binding? It's a brainstorming, to be honest, I am also blinded, and the next may or may not be correct, but it is always true.

analysis

First look at the bytecode of the main method above:

 // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 19 L0
    NEW com/learn/pra06/Demo01
    DUP
    INVOKESPECIAL com/learn/pra06/Demo01.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 20 L1
    NEW com/learn/pra06/Demo02
    DUP
    INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 21 L2
    NEW com/learn/pra06/Demo02
    DUP
    INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
    ASTORE 3
   L3
    LINENUMBER 22 L3
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
   L4
    LINENUMBER 23 L4
    INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
   L5
    LINENUMBER 24 L5
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/Demo02.method1 ()V
   L6
    LINENUMBER 25 L6
    INVOKESTATIC com/learn/pra06/Demo02.method2 ()V
   L7
    LINENUMBER 26 L7
    ALOAD 3
    INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()V
   L8
    LINENUMBER 27 L8
    INVOKESTATIC com/learn/pra06/Demo01.method2 ()V
   L9
    LINENUMBER 28 L9
    RETURN
   L10

  L+number corresponds to each line in the body of the main method. We can clearly see the instructions executed by the code. It is simply love, there is a kind of rush to meet each other late.
  Demo01 d1= new Demo01();What will happen to this statement at runtime? Combine the instruction sets we prepared to learn in the early stage. Looking at the above bytecode, INVOKESPECIAL com/learn/pra06/Demo01.<init> ()VI found that: I would like to ask the constructor of Demo01 to be called, there is no doubt about this. The same is true for L1.
  Demo01 d3= new Demo02();Although the declared type is a parent class, it is actually a subclass when new, and the same bytecode corresponds to this. INVOKESPECIAL com/learn/pra06/Demo02.<init> ()V
  d1.method1();This statement should be the object calling its instance method. The bytecode also illustrates this point: INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()VINVOKEVIRTUAL is used here, which means the virtual method is called, and the reference of this method is stored in the method table (more on this later), Only the INVOKEVIRTUAL instruction will go to the method table to find the reference of the method to be called .
  d1.method2();This sentence is that the object calls a static method. The bytecode is: INVOKESTATIC com/learn/pra06/Demo01.method2 ()VThis method is to directly call the static method in the method area without going through the method table. This also explains that the execution of the static method only depends on the static type, and has nothing to do with the actual type. And because the overridden method calls look at the actual type, static methods cannot be overridden. The two method call explanations of d2 are the same as d1.
  The focus is on the calling process of the d3 method. d3.method1();Bytecode: The INVOKEVIRTUAL com/learn/pra06/Demo01.method1 ()VINVOKEVIRTUAL instruction is used, indicating that the method that is actually pointed to will be called in the method table during operation. Because method01 may be rewritten, the compiler indicates that method1 should be called at runtime. A reference to the real method stored in the method table. Because the method01 method was rewritten by Demo02, the reference of the method01 method of the parent class in the method table was rewritten to the method01 method of the subclass, so the method01 method found according to the INVOKEVIRTUAL instruction at runtime is a subclass.
  Then find d3.method2();by looking at the bytecode:INVOKESTATIC com/learn/pra06/Demo01.method2 ()VWhen INVOKESTATIC is used, the method table cannot be accessed, but the method2 of the parent class is directly accessed, so the static method of the parent class is called at runtime.

The static type (declared type) of the object is taken as the receiver of the method at compile time. During operation, changes are made according to the instruction set.

INVOKEVIRTUAL instruction flow

package com.learn.pra06;
public class ClassReference {
    static class Person {
        @Override
        public String toString(){
            return "I'm a person.";
        }
        public void eat(){
            System.out.println("Person eat");
        }
        public void speak(){
            System.out.println("Person speak");
        }

    }
    static class Boy extends Person{
        @Override
        public String toString(){
            return "I'm a boy";
        }
        @Override
        public void speak(){
            System.out.println("Boy speak");
        }
        public void fight(){
            System.out.println("Boy fight");
        }
    }
    static class Girl extends Person{
        @Override
        public String toString(){
            return "I'm a girl";
        }
        @Override
        public void speak(){
            System.out.println("Girl speak");
        }
        public void sing(){
            System.out.println("Girl sing");
        }
    }
    public static void main(String[] args) {
        Person boy = new Boy();
        Person girl = new Girl();
        System.out.println(boy);
        boy.eat();
        boy.speak();
        System.out.println(girl);
        girl.eat();
        girl.speak();
    }
}

Execution result:
Write picture description here
  Because Boy and Girl did not override the parent class Person eat method, they would call the parent class's eat method.
Bytecode:

  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 47 L0
    NEW com/learn/pra06/ClassReference$Boy
    DUP
    INVOKESPECIAL com/learn/pra06/ClassReference$Boy.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 48 L1
    NEW com/learn/pra06/ClassReference$Girl
    DUP
    INVOKESPECIAL com/learn/pra06/ClassReference$Girl.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 49 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L3
    LINENUMBER 50 L3
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
   L4
    LINENUMBER 51 L4
    ALOAD 1
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
   L5
    LINENUMBER 53 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L6
    LINENUMBER 54 L6
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.eat ()V
   L7
    LINENUMBER 55 L7
    ALOAD 2
    INVOKEVIRTUAL com/learn/pra06/ClassReference$Person.speak ()V
   L8
    LINENUMBER 57 L8
    RETURN
   L9

Obviously at L2, the compiler will write the reference to the toString method of the root class Object into the class file, indicating that the compiler will write the ancestor's method reference instead of the close relative.
L3, L4, L6, and L7 all use INVOKEVIRTUAL, what is the process?
First look at the memory model of the method table:
Write picture description here
by looking at the Girl and Boy method tables, you can see that the inherited methods are arranged from beginning to end, and the method references have fixed indexes in the subclasses, that is, they all have the same offset ; If the subclass rewrites a method of the parent class, it will make the method reference of the subclass method table originally stored in the parent class become the reference of the rewritten method. At this point, it should be understood why the correct method can be called according to the object type The key to the method lies in the method table . Take the
following as girl.speakan example, take a look at the INVOKEVIRTUAL instruction flow
Write picture description here
explanation diagram:
1. First, INVOKEVIRTUAL com/learn/pra06/ClassReferencePerson.speak()V, according to com/learn/pra06/ClassReference

Person.speak ()V find the offset of the method in the constant pool
2. Check the method table of Person to get the offset of the speak method in the method table (assuming it is 15), so that you can get a direct reference to the method .
3. According to this, judge that the reference refers to the Girl instance
4. Then go to the method table of the Girl instance, and find the method reference in the method table according to the offset above, because the value of the method reference is loaded in the class according to whether Rewriting the method has determined the correct method reference, so we can directly call the method here.

Why can't static methods hide instance methods?

The invocation of the static method is statically bound in the compiler, and the invocation of the instance method is dynamically bound at runtime. The two methods of invocation are different, so only one of the two can exist, otherwise there will be ambiguity. !

Why can static methods hide static methods?

Because the calling method is the same, it will not cause ambiguity as above. Although the parent class and the subclass define the same function, the compiler will activate the reference of the corresponding static method according to the static type of the object, causing the illusion of overwriting. Not a rewrite!

to sum up

The overall process is: the compiler compiles the class into a class file, in which the method will write the corresponding method reference into the class according to the static type. At runtime, the JVM will find the deviation of the method in the constant pool according to the method reference pointed to by INVOKEVIRTUAL Shift, then find the object actually pointed to by the reference type according to this, access the method table of this object type, find the location where the target method reference is stored according to the offset, take out the reference, call the method actually pointed to by the reference, and complete the polymorphism

Guess you like

Origin blog.csdn.net/qq_32907195/article/details/112294312