合成元素是存在于编译的类文件中但不存在于编译它的源代码中的任何元素。通过检查某个元素是否是合成的,您可以区分这些元素以用于反射处理代码的工具。这当然首先与使用反射的库相关,但也与其他工具(例如 IDE)相关,这些工具不允许您调用合成方法或使用合成类。最后,对于 Java 编译器来说,在编译期间验证代码不要直接使用合成元素也很重要。合成元素仅用于使 Java 运行时满意,它只是处理(并验证)所交付的代码,其中合成元素与任何其他元素的处理方式相同。
您已经提到内部类作为 Java 编译器插入合成元素的示例,所以让我们看一下这样的类:
class Foo {
private String foo;
class Bar {
private Bar() { }
String bar() {
return foo;
}
}
Bar bar() {
return new Bar();
}
}
编译效果非常好,但如果没有合成元素,它会被对内部类一无所知的 JVM 拒绝。 Java编译器德糖雷斯上面的类类似于以下内容:
class Foo {
private String foo;
String access$100() { // synthetic method
return foo;
}
Foo$Bar bar() {
return new Foo$Bar(this, (Foo$1)null);
}
Foo() { } // NON-synthetic, but implicit!
}
class Foo$Bar {
private final Foo $this; // synthetic field
private Foo$Bar(Foo $this) { // synthetic parameter
this.$this = $this;
}
Foo$Bar(Foo $this, Foo$1 unused) { // synthetic constructor
this($this);
}
String bar() {
return $this.access$100();
}
}
class Foo$1 { /*empty, no constructor */ } // synthetic class
如前所述,JVM 不知道内部类,但强制执行成员的私有访问,即内部类将无法访问其封闭类的私有属性。因此,Java 编译器需要向被访问的类添加所谓的访问器,以公开其不可见属性:
The foo
字段是私有的,因此只能从内部访问Foo
. The access$100
方法将此字段公开给其包,在该包中始终可以找到内部类。该方法是合成的,因为它是由编译器添加的。
The Bar
构造函数是私有的,因此只能从其自己的类中调用。为了实例化一个实例Bar
,另一个(合成)构造函数需要公开实例的构造。然而,构造函数有一个固定的名称(在内部,它们都被称为<init>
),因此我们不能将这种技术应用于我们简单命名的方法访问器access$xxx
。相反,我们通过创建合成类型来使构造函数访问器变得唯一Foo$1
.
为了访问其外部实例,内部类需要存储对此实例的引用,该引用存储在合成字段中$this
。该引用需要通过构造函数中的合成参数传递给内部实例。
合成元素的其他示例包括表示 lambda 表达式的类、使用不同类型签名重写方法时的桥接方法、创建Proxy
类或由其他工具(例如 Maven 构建或运行时代码生成器)创建的类,例如字节好友 http://bytebuddy.net(无耻的插头)。