如果你用过反射并且执行过 getDeclaredMethods 办法的话,你可能会感到很吃惊。你会发现呈现了很多源代码里没有的办法。如果你看一下这些办法的修饰符的话,可能会发现外面有些办法是 volatile 的。顺便说一句,如果在 Java 面试里问到“什么是 volatile 办法?”,你可能会吓出一身冷汗。正确的答案是没有 volatile 办法。但同时,getDeclaredMethods() 或者 getMethods() 返回的这些办法,Modifier.isVolatile(method.getModifiers()) 的后果却是 true。
一些用户遇到过这样的问题。他们发现,应用 immutator(这个我的项目摸索了 Java 的一些鲜为人知的细节)生成的 Java 代码应用 volatile 了作为办法的关键字,而这样的代码没法通过编译。后果就是这基本没法用。
这是怎么回事?syntethic 和 bridge 办法又是什么?
可见性
当你创立一个嵌套类的时候,它的公有变量和办法对下层的类是可见的。这个在不可变嵌套式 Builder 模式中用到了。这是 Java 语言标准里曾经定义好的一个行为。
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);
}
}
JVM 是如何解决这个的?它可不晓得什么是外部类或者嵌套类的。JVM 对所有的类都厚此薄彼,它都认为是顶级类。所有类都会被编译成顶级类,而那些外部类编译完后会生成…$… class 的类文件。
$ ls -Fart
../ SyntheticMethodTest2$A.class MyClass.java SyntheticMethodTest4.java SyntheticMethodTest2.java
SyntheticMethodTest2.class SyntheticMethodTest3.java ./ MyClassSon.java SyntheticMethodTest1.java
如果你创立一个外部类的话,它会被彻底编译成一个顶级类。
那这些公有变量又是如何被外部类拜访的呢?如果它们是个顶级类的公有变量(它们确实也是),那为什么别的类还能间接拜访这些变量?
javac 是这样解决这个问题的,对于任何 private 的字段,办法或者构造函数,如果它们也被其它顶层类所应用,就会生成一个 synthetic 办法。这些 synthetic 办法是用来拜访最后的公有变量 / 办法 / 构造函数的。这些办法的生成也很智能:只有的确被外部类用到了,才会生成这样的办法。
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());
}
}
}
这些生成的办法的名字取决于具体的实现,最初叫什么也不好说。我只能说在我运行的这个平台上,上述程序的输入是这样的:
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
在下面这个程序中,咱们给变量 x 赋值,而后又调用了一个同名的办法。这会触发编译器生成对应的 synthetic 办法。你会看到它生成了三个办法,应该是 x 变量的 setter 和 getter 办法,以及 x() 办法对应的一个 synthetic 办法。这些办法并不存在于 getMethods 办法里返回的列表中,因为它们是 synthetic 办法,是不能间接被调用的。从这点来看,它们和公有办法差不多。
看一下 java.lang.reflect.Modifier 外面定义的常量,能够明确这些十六进制的数字代表的是什么:
00001008 SYNTHETIC|STATIC
00000002 PRIVATE
00000111 NATIVE|FINAL|PUBLIC
00000011 FINAL|PUBLIC
00000001 PUBLIC
00001000 SYNTHETIC
列表中有两个是构造方法。还有一个公有办法以及一个 synthetic 办法。存在这个公有办法是因为咱们的确定义了它。而 synthetic 办法的呈现是因为咱们从外部类调用了它外部的公有成员。到目前为止,还没有呈现过 bridge 办法。
泛型和继承
到目前为止,看起来还不错。不过咱们还没有看到”volatile”办法。
看一下 java.lang.reflect.Modifier 的源码你会发现 0x00000040 这个常量被定义了两次。一次是定义成 VOLATILE,还有一次是 BRIDGE(后者是包外部公有的,并不对外开放)。
想呈现 volatile 办法的话,写个简略的程序就行了:
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());
}
}
}
这个链表有一个返回 String 的 get(int) 办法。先别探讨代码整不整洁的问题了。这只是段示例代码而已。整洁的代码当然也会呈现同样的问题,不过越简单的代码越难定位问题罢了。
输入的后果是这样的:
00000001 String get
00001041 Object get
这里有两个 get 办法。一个是代码里的那个,另外一个是 synthetic 和 bridge 办法。用 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
乏味的是,两个办法的签名是截然不同的,只有返回类型不同。这个在 JVM 外面是非法的,不过在 Java 语言里可不容许。bridge 的这个办法不干别的,就只是去调用了下原始的那个办法。
为什么咱们须要这个 synthetic 办法呢,谁会调用它?比方当初有段代码想要调用一个非 MyLink 类型变量的 get(int) 办法:
List<?> a = new MyLink();
Object z = a.get(0);
它不能调用返回 String 的办法,因为 List 里没这样的办法。为了解释的更分明一点,咱们重写下 add 办法而不是 get 办法:
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);
}
}
咱们会发现这个 bridge 办法
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
它不仅调用了原始的办法,它还进行了类型查看。这个查看是在运行时进行的,并不是由 JVM 本人来实现。正如你所想,在 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)
下次如果你在面试中被问到 volatile 办法的话,说不定面试官晓得的还没你多:-) 私信”学习“有惊喜