java1618解决方法_Java那些不为人知的特殊方法

如果你用过反射并且执行过 getDeclaredMethods 方法的话,你可能会感到很吃惊。你会发现出现了很多源代码里没有的方法。如果你看一下这些方法的修饰符的话,可能会发现里面有些方法是 volatile 的。顺便说一句,如果在 Java 面试里问到“什么是 volatile 方法?”,你可能会吓出一身冷汗。正确的答案是没有 volatile 方法。但同时,getDeclaredMethods()或者 getMethods() 返回的这些方法,Modifier.isVolatile(method.getModifiers()) 的结果却是 true。

immutator的一些用户遇到过这样的问题。他们发现,使用 immutator(这个项目探索了 Java 的一些不为人知的细节)生成的 Java 代码使用 volatile 了作为方法的关键字,而这样的代码没法通过编译。结果就是这根本没法用。

这是怎么回事?syntethic 和 bridge 方法又是什么?

可见性

当你创建一个嵌套类的时候,它的私有变量和方法对上层的类是可见的。这个在不可变嵌套式 Builder 模式中用到了。这是 Java 语言规范里已经定义好的一个行为。

01

package synthetic;

02

03

public class SyntheticMethodTest1 {

04

private A aObj =new A();

05

06

public class A {

07

private int i;

08

}

09

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 对所有的类都一视同仁,它都认为是顶级类。所有类都会被编译成顶级类,而那些内部类编译完后会生成…$… 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 的字段,方法或者构造函数,如果它们也被其它顶层类所使用,就会生成一个 synthetic 方法。这些 synthetic 方法是用来访问最初的私有变量 / 方法 / 构造函数的。这些方法的生成也很智能:只有确实被外部类用到了,才会生成这样的方法。

01

package synthetic;

02

03

import java.lang.reflect.Constructor;

04

import java.lang.reflect.Method;

05

06

public class SyntheticMethodTest2 {

07

08

public static class A {

09

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

}

这些生成的方法的名字取决于具体的实现,最后叫什么也不好说。我只能说在我运行的这个平台上,上述程序的输出是这样的:

01

2

02

00001008 access$1

03

00001008 access$2

04

00001008 access$3

05

00000002 x

06

--------------------------

07

00000111 void wait

08

00000011 void wait

09

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 方法。这些方法并不存在于 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 方法的话,写个简单的程序就行了:

01

package synthetic;

02

03

import java.lang.reflect.Method;

04

import java.util.LinkedList;

05

06

public class SyntheticMethodTest3 {

07

08

public static class MyLinkextends LinkedList {

09

@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 反编译后会是这样的:

01

public java.lang.String get(int);

02

Code:

03

Stack=1, Locals=2, Args_size=2

04

0:   ldc     #2;//String

05

2:   areturn

06

LineNumberTable:

07

line12:0

08

09

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);

它不能调用返回 String 的方法,因为 List 里没这样的方法。为了解释的更清楚一点,我们重写下 add 方法而不是 get 方法:

01

package synthetic;

02

03

import java.util.LinkedList;

04

import java.util.List;

05

06

public class SyntheticMethodTest4 {

07

08

public static class MyLinkextends LinkedList {

09

@Override

10

public boolean add(String s) {

11

return true;

12

}

13

}

14

15

public static void main(String[] args) {

16

List a =new MyLink();

17

a.add("");

18

a.add(13);

19

}

20

}

我们会发现这个 bridge 方法

1

public boolean add(java.lang.Object);

2

Code:

3

Stack=2, Locals=2, Args_size=2

4

0:   aload_0

5

1:   aload_1

6

2:   checkcast       #2;//class java/lang/String

7

5:   invokevirtual   #3;//Method add:(Ljava/lang/String;)Z

8

8:   ireturn

它不仅调用了原始的方法,它还进行了类型检查。这个检查是在运行时进行的,并不是由 JVM 自己来完成。正如你所想,在 18 行的地方会抛出一个异常:

1

Exception in thread"main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

2

at synthetic.SyntheticMethodTest4$MyLink.add(SyntheticMethodTest4.java:1)

3

at synthetic.SyntheticMethodTest4.main(SyntheticMethodTest4.java:18)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值