Some unknown special methods in Java may not be known to the interviewer after you learn it!

If you have used reflection and executed the getDeclaredMethods method, you may be surprised. You will find that there are many methods that are not in the source code. If you look at the modifiers of these methods, you may find that some of them are volatile. By the way, if you ask "What is a volatile method?" in a Java interview, you may get a cold sweat. The correct answer is that there is no volatile method. But at the same time, for these methods returned by getDeclaredMethods() or getMethods(), the result of Modifier.isVolatile(method.getModifiers()) is true.

Some users have encountered such problems. They found that the Java code generated using immutator (this project explores some unknown details of Java) uses volatile as a method keyword, and such code cannot be compiled. The result is that it doesn't work at all.

How is this going? What are the syntethic and bridge methods?

Visibility

When you create a nested class, its private variables and methods are visible to the upper class. This is used in the immutable nested Builder pattern. This is a behavior that has been defined in the Java language specification.

package synthetic;

public class SyntheticMethodTest1 {
    private A aObj = new A();

    public class A {
        private int i;
    }

    private class B {
        private int i = aObj.i;
    }

    public static void main(String[] args) {
        SyntheticMethodTest1 me = new SyntheticMethodTest1();
        me.aObj.i = 1;
        B bObj = me.new B();
        System.out.println(bObj.i);
    }
}

How does the JVM handle this? It doesn't know what an inner class or nested class is. JVM treats all classes equally, and it considers them to be top-level classes. All classes will be compiled into top-level classes, and those inner classes will be compiled into class files of...$...class.

$ ls -Fart
../                         SyntheticMethodTest2$A.class  MyClass.java  SyntheticMethodTest4.java  SyntheticMethodTest2.java
SyntheticMethodTest2.class  SyntheticMethodTest3.java     ./            MyClassSon.java            SyntheticMethodTest1.java

If you create an inner class, it will be completely compiled into a top-level class.

How are these private variables accessed by external classes? If they are private variables of a top-level class (and they are), then why can other classes directly access these variables?

This is how javac solves this problem. For any private fields, methods or constructors, if they are also used by other top-level classes, a synthetic method will be generated. These synthetic methods are used to access the original private variables/methods/constructors. The generation of these methods is also very smart: such methods will only be generated when they are actually used by external classes.

package synthetic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class SyntheticMethodTest2 {

    public static class A {
        private A(){}
        private int x;
        private void x(){};
    }

    public static void main(String[] args) {
        A a = new A();
        a.x = 2;
        a.x();
        System.out.println(a.x);
        for (Method m : A.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getName());
        }
        System.out.println("--------------------------");
        for (Method m : A.class.getMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
        System.out.println("--------------------------");
        for( Constructor<?> c : A.class.getDeclaredConstructors() ){
            System.out.println(String.format("%08X", c.getModifiers()) + " " + c.getName());
        }
    }
}

The names of these generated methods depend on the specific implementation, and the final name is not easy to say. I can only say that on the platform I am running, the output of the above program is like this:

2
00001008 access$1
00001008 access$2
00001008 access$3
00000002 x
--------------------------
00000111 void wait
00000011 void wait
00000011 void wait
00000001 boolean equals
00000001 String toString
00000101 int hashCode
00000111 Class getClass
00000111 void notify
00000111 void notifyAll
--------------------------
00000002 synthetic.SyntheticMethodTest2$A
00001000 synthetic.SyntheticMethodTest2$A

In the above program, we assign a value to the variable x, and then call a method with the same name. This will trigger the compiler to generate the corresponding synthetic method. You will see that it generates three methods, which should be the setter and getter methods of the x variable, and a synthetic method corresponding to the x() method. These methods do not exist in the list returned by the getMethods method, because they are synthetic methods and cannot be called directly. From this point of view, they are similar to private methods.

Looking at the constants defined in java.lang.reflect.Modifier, you can understand what these hexadecimal numbers represent:

00001008 SYNTHETIC|STATIC
00000002 PRIVATE
00000111 NATIVE|FINAL|PUBLIC
00000011 FINAL|PUBLIC
00000001 PUBLIC
00001000 SYNTHETIC

There are two construction methods in the list. There is also a private method and a synthetic method. This private method exists because we did define it. The synthetic method appears because we call its internal private members from an external class. So far, there has not been a bridge method.

Generics and inheritance

So far, it looks pretty good. But we have not seen the "volatile" method.

Look at the source code of java.lang.reflect.Modifier and you will find that the constant 0x00000040 is defined twice. Once it was defined as VOLATILE, and once it was BRIDGE (the latter is private inside the package and not open to the outside world).

If you want to have a volatile method, just write a simple program:

package synthetic;

import java.lang.reflect.Method;
import java.util.LinkedList;

public class SyntheticMethodTest3 {

    public static class MyLink extends LinkedList {
        @Override
        public String get(int i) {
            return "";
        }
    }

    public static void main(String[] args) {

        for (Method m : MyLink.class.getDeclaredMethods()) {
            System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
        }
    }
}

This linked list has a get(int) method that returns String. Don't discuss the issue of untidy code. This is just a sample code. Of course, neat code will have the same problem, but the more complex the code, the harder it is to locate the problem.

The output result is this:

00000001 String get
00001041 Object get

There are two get methods. One is the one in the code, and the other is the synthetic and bridge methods. It will look like this after decompiling with javap:

public java.lang.String get(int);
  Code:
   Stack=1, Locals=2, Args_size=2
   0:   ldc     #2; //String
   2:   areturn
  LineNumberTable:
   line 12: 0

public java.lang.Object get(int);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   iload_1
   2:   invokevirtual   #3; //Method get:(I)Ljava/lang/String;
   5:   areturn

Interestingly, the signatures of the two methods are exactly the same, only the return type is different. This is legal in JVM, but not allowed in Java language. The bridge method does nothing else, it just calls the original method.

Why do we need this synthetic method, and who will call it? For example, there is a piece of code that wants to call the get(int) method of a non-MyLink variable:

List<?> a = new MyLink();
        Object z = a.get(0);

It cannot call methods that return String, because there is no such method in List. In order to explain more clearly, we rewrite the add method instead of the get method:

package synthetic;

import java.util.LinkedList;
import java.util.List;

public class SyntheticMethodTest4 {

    public static class MyLink extends LinkedList {
        @Override
        public boolean add(String s) {
            return true;
        }
    }

    public static void main(String[] args) {
        List a = new MyLink();
        a.add("");
        a.add(13);
    }
}

We will find this bridge method

public boolean add(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   checkcast       #2; //class java/lang/String
   5:   invokevirtual   #3; //Method add:(Ljava/lang/String;)Z
   8:   ireturn

Not only does it call the original method, it also performs type checking. This check is performed at runtime, not by the JVM itself. As you might expect, an exception will be thrown at line 18:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at synthetic.SyntheticMethodTest4$MyLink.add(SyntheticMethodTest4.java:1)
    at synthetic.SyntheticMethodTest4.main(SyntheticMethodTest4.java:18)

Next time if you are asked about the volatile method in an interview, maybe the interviewer doesn’t know as much as you:-) There are surprises in the private message "learning"

file

Guess you like

Origin blog.csdn.net/weixin_46577306/article/details/108064818