有这样的课:
public class Sample1 {
public class Inner {
private int f;
}
void go() {
Inner in = new Inner();
int value = in.f;
}
}
go
方法(java-11之前)的字节码调用了已知的合成方法:
static int access$000(nestmates.Sample1$Inner);
descriptor: (Lnestmates/Sample1$Inner;)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field f:I
4: ireturn
发件人:
0: new #2 // class nestmates/Sample1$Inner
3: dup
4: aload_0
5: invokespecial #3 // Method nestmates/Sample1$Inner."<init>":(Lnestmates/Sample1;)V
8: astore_1
9: aload_1
10: invokestatic #4 // Method nestmates/Sample1$Inner.access$000:(Lnestmates/Sample1$Inner;)I
我知道这件事已经有一段时间了,但是我从来没有问过自己 * 为什么 * 会这样?为什么javac必须首先这样做,为什么不直接使用getField
和通过静态方法的间接访问。谢谢。
3条答案
按热度按时间z2acfund1#
因为JVMS不允许访问不同类的私有成员,不管它是否是内部类。JDK 11添加了nestmates的概念来克服这个限制。
ujv3wf0j2#
原因是外部类和内部类编译到不同的类文件,这意味着它们不能访问彼此的私有成员。
生成合成方法是为了有效地将访问从私有扩展到包私有,但它是通过不必要的间接实现的。
在Java 11中,他们为此引入了一个新概念,允许不同文件中的类在某些情况下(例如,这个)访问彼此的私有成员。
参见JEP:https://openjdk.org/jeps/181
eiee3dmh3#
这是一个简单的结论,基于许多因素:
1.在JVM中(class file /
java.exe
)级别,**内部类根本不存在。**Javac通过将Inner
类命名为Sample1$Inner
来“伪造”它(美元不是一个呈现,而是它实际JVM级别名称,$只是一个符号,与I
或n
一样有效),将Sample1
类型的参数作为第一参数添加到Inner的所有构造函数,用new Sample1(this)
替换new Sample1()
,用new Sample1(instanceOfOuter)
替换instanceOfOuter.new Sample1()
,具有Sample1
类型的final
字段(这些构造函数通过使用第一个参数设置),将对外部方法的所有调用转换为在该字段上调用(假设在JVM级别没有“outter this”,因为没有外部类),等等。使用javap -c
可以看到所有这些。private
成员不能被除自身之外的任何类型访问,因此,假设内部类在编译结束后就不再是内部类(因为JVM首先没有内部类的概念),它需要一种包私有(或者受保护或公共,根据您的需要)的方式来访问它。javac
知道synthetic是什么意思。也就是说:标记您创建的任何内容,以便将精心管理的伪代码粘合在一起,使内部类看起来像是一个纯粹的Java语言(即编译器)特性,并且不存在于java运行时级别--并且在阅读类文件时,对它们视而不见。就像这些合成方法不存在一样。例如,如果javac被要求编译试图调用合成方法的代码,操作方式与编译尝试调用不存在的方法的代码相同。正如一个评论已经指出的,java do 的最新版本在JVM级别引入了内部类的概念,但不是通过在JVM规范中体现“内部类”的概念,而是通过“嵌套”的概念,它允许类文件列出其他可以“看到”私有元素并调用/与私有元素交互的类名。如果目标是现代JVM(“现代”在此定义为:“有可用的巢友功能”),将使用巢友和放弃所有的合成。