如果你用过反射并且执行过 getDeclaredMethods方法的话,你可能会感到很惊讶。你会发现很多源代码里没有的方法。或许你也看过到这些方法的一些修饰符,并且发现里面有的方法是volatile的。
可见性
当你创建一个内部的或者说嵌套的时候,这个类的私有变量和方法对上层的类是可见的。这个在不可变嵌套式Builder模式中用到了。这在Java语言规范里是定义好的一个行为。
1. package synthetic;
2.
3. public class SyntheticMethodTest1 {
4. private A aObj = new A();
5.
6. public class A {
7. private int i;
8. }
9.
10. private class B {
11. private int i = aObj.i;
12. }
13.
14. public static void main(String[] args) {
15. SyntheticMethodTest1 me = new SyntheticMethodTest1();
16. me.aObj.i = 1;
17. B bObj = me.new B();
18. System.out.println(bObj.i);
19. }
20. }
JVM是如何处理这个的?JVM是不知道类是内部的还是说嵌套的。JVM对所有的类对一视同仁,都认为是顶层的。所有的类都会被编译的顶层的类,那些内部类编译完后会生成...$... class的类文件。
1. $ ls -Fart
2. ../ SyntheticMethodTest2$A.class MyClass.java SyntheticMethodTest4.java SyntheticMethodTest2.java
3. SyntheticMethodTest2.class SyntheticMethodTest3.java ./ MyClassSon.java SyntheticMethodTest1.java
如果你创建一个内部的类的话,编译完后它其实就是个完全的顶层的类。
那这些私有变量是如何被外部类访问的呢?如果它们是个顶层类的私有变量,它们的确也是,那为什么别的类还能直接访问这些变量?
javac是这样解决这个问题的,对于那些声明为private 的字段,方法或者构造函数,如果它们还被外部类所使用,就会生成一个sythetic的方法。这些sythetic方法是用来访问最终的私有变量/方法 /构造函数的。这些方法的生成也很智能,只有那些确实被外部类用到的才会生成这样的方法。
1. package synthetic;
2.
3. import java.lang.reflect.Constructor;
4. import java.lang.reflect.Method;
5.
6. public class SyntheticMethodTest2 {
7.
8. public static class A {
9. private A(){}
10. private int x;
11. private void x(){};
12. }
13.
14. public static void main(String[] args) {
15. A a = new A();
16. a.x = 2;
17. a.x();
18. System.out.println(a.x);
19. for (Method m : A.class.getDeclaredMethods()) {
20. System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getName());
21. }
22. System.out.println("--------------------------");
23. for (Method m : A.class.getMethods()) {
24. System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
25. }
26. System.out.println("--------------------------");
27. for( Constructor<?> c : A.class.getDeclaredConstructors() ){
28. System.out.println(String.format("%08X", c.getModifiers()) + " " + c.getName());
29. }
30. }
31. }
生成的这些方法的名字都取决于具体的实现,最后叫什么也不好说。我只能说在我运行的这个平台上,上述程序的输出是这样的:
1. 2
2. 00001008 access$1
3. 00001008 access$2
4. 00001008 access$3
5. 00000002 x
6. --------------------------
7. 00000111 void wait
8. 00000011 void wait
9. 00000011 void wait
10. 00000001 boolean equals
11. 00000001 String toString
12. 00000101 int hashCode
13. 00000111 Class getClass
14. 00000111 void notify
15. 00000111 void notifyAll
16. --------------------------
17. 00000002 synthetic.SyntheticMethodTest2$A
18. 00001000 synthetic.SyntheticMethodTest2$A
在上面这个程序中,我们把值赋给了变量x,然后又调用 了同名的一个方法。这会触发编译器来生成对应的synthetic方法。你会看到它生成了三个方法,应该是x变量的setter和getter方法,以及 x()方法的一个synthetic方法。这些synthetic方法并不存在于getMethods方法里返回的列表中,因为这些是synthetic 方法,它们是不能直接调用的。从这点来说,它们和私有方法差不多。
看一下java.lang.reflect.Modifier里面定义的常量,可以明白这些十六进制的数字代表的是什么:
1. 00001008 SYNTHETIC|STATIC
2. 00000002 PRIVATE
3. 00000111 NATIVE|FINAL|PUBLIC
4. 00000011 FINAL|PUBLIC
5. 00000001 PUBLIC
6. 00001000 SYNTHETIC
列表中有两个是构造方法。还有一个私有方法和一个synthetic的。私有的这个是因为我们确实定义了。synthetic的方法出现是因为我们从外部调用了内部的私有成员。这里面还没有出现bridge方法。
泛型和继承
到现在为止看起来还不错。不过我们还没有看到”volatile”的方法。
看一下java.lang.reflect.Modifier的源码你会发现0x00000040这个常量定义了两次。一次是定义成VOLATILE,还有一次是BRIDGE(后者是包内部私有的,并不对外开放)。
想出现volatile的方法,只需要写个简单的程序 就行了:
1. package synthetic;
2.
3. import java.lang.reflect.Method;
4. import java.util.LinkedList;
5.
6. public class SyntheticMethodTest3 {
7.
8. public static class MyLink extends LinkedList<String> {
9. @Override
10. public String get(int i) {
11. return "";
12. }
13. }
14.
15. public static void main(String[] args) {
16.
17. for (Method m : MyLink.class.getDeclaredMethods()) {
18. System.out.println(String.format("%08X", m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName());
19. }
20. }
21. }
我们的这个链表,有一个返回String的get(int)方法。先别讨论代码整洁的问题了。这只是段演示这个主题的示例代码而已。简洁的代码当然也同样会出现问题,不过越复杂的代码越难发现问题罢了。
输出 是这样的:
1. 00000001 String get
2. 00001041 Object get
我们有两个get方法。一个是代码里的这个,另外一个是synthetic和bridge的方法。用javap反编译后会是这样的:
1. public java.lang.String get(int);
2. Code:
3. Stack=1, Locals=2, Args_size=2
4. 0: ldc #2; //String
5. 2: areturn
6. LineNumberTable:
7. line 12: 0
8.
9. public java.lang.Object get(int);
10. Code:
11. Stack=2, Locals=2, Args_size=2
12. 0: aload_0
13. 1: iload_1
14. 2: invokevirtual #3; //Method get:(I)Ljava/lang/String;
15. 5: areturn
有趣的是,两个方法的签名是一模一样的,只有返回类型不同。这个在JVM里面是允许的,不过在Java语言里是不行的。bridge的这个方法别的啥也不干,就只是调用了下原始的那个方法。
为什么我们需要这个synthetic方法呢?谁来调用它。比如现在有段代码想要调用一个非MyLink类型变量的get(int)方法:
1. List<?> a = new MyLink();
2. Object z = a.get(0);
由于在修饰符中volatile和bridge是同一个值,在之前版本的javap中存在一个BUG,一个bridge方法在反编译后会显示成volatile,所以存在”volatile方法”的说法。