如果你用过反射并且执行过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.javaSyntheticMethodTest2.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()); } }}
这些生成的办法的名字取决于具体的实现,最初叫什么也不好说。我只能说在我运行的这个平台上,上述程序的输入是这样的:
200001008 access$100001008 access$200001008 access$300000002 x--------------------------00000111 void wait00000011 void wait00000011 void wait00000001 boolean equals00000001 String toString00000101 int hashCode00000111 Class getClass00000111 void notify00000111 void notifyAll--------------------------00000002 synthetic.SyntheticMethodTest2$A00001000 synthetic.SyntheticMethodTest2$A
在下面这个程序中,咱们给变量x赋值,而后又调用了一个同名的办法。这会触发编译器生成对应的synthetic办法。你会看到它生成了三个办法,应该是x变量的setter和getter办法,以及x()办法对应的一个synthetic办法。这些办法并不存在于getMethods办法里返回的列表中,因为它们是synthetic办法,是不能间接被调用的。从这点来看,它们和公有办法差不多。
看一下java.lang.reflect.Modifier外面定义的常量,能够明确这些十六进制的数字代表的是什么:
00001008 SYNTHETIC|STATIC00000002 PRIVATE00000111 NATIVE|FINAL|PUBLIC00000011 FINAL|PUBLIC00000001 PUBLIC00001000 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 get00001041 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: 0public 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办法的话,说不定面试官晓得的还没你多:-) 私信”学习“有惊喜