1. 概述
在本篇文章中,我们将深入探讨 Java 中由编译器生成的合成构造(synthetic constructs)。这些构造是编译器为了透明地访问那些由于可见性不足或缺少引用而无法直接访问的成员时,自动引入的代码。
⚠️ 从 JDK 11 开始,合成方法和构造器不再被生成,取而代之的是 基于嵌套的访问控制(nest-based access control)。
2. Java 中的合成构造
根据 Java 语言规范(JLS 13.1.7)中的定义:
任何由 Java 编译器引入但源代码中并不存在的构造,都必须标记为合成的(synthetic),除了默认构造器、类初始化方法以及枚举类的
values
和valueOf
方法。
合成构造主要包括:合成字段(fields)、合成方法(methods) 和 合成构造器(constructors)。
虽然编译器可能会对嵌套类(如匿名类)进行修改,但嵌套类本身并不被视为合成构造。
接下来,我们分别来看这三类合成构造的具体表现。
3. 合成字段
我们先来看一个简单的内部类示例:
public class SyntheticFieldDemo {
class NestedClass {}
}
当这个类被编译时,每个内部类都会包含一个合成字段,用于引用其外部类实例。这正是内部类可以访问外部类成员的关键机制。
我们可以通过反射来验证这一点:
public void givenSyntheticField_whenIsSynthetic_thenTrue() {
Field[] fields = SyntheticFieldDemo.NestedClass.class
.getDeclaredFields();
assertEquals("This class should contain only one field",
1, fields.length);
for (Field f : fields) {
System.out.println("Field: " + f.getName() + ", isSynthetic: " +
f.isSynthetic());
assertTrue("All the fields of this class should be synthetic",
f.isSynthetic());
}
}
使用 javap
命令反汇编后,可以看到这个合成字段的名字是 this$0
。
4. 合成方法
我们给内部类添加一个私有字段:
public class SyntheticMethodDemo {
class NestedClass {
private String nestedField;
}
public String getNestedField() {
return new NestedClass().nestedField;
}
public void setNestedField(String nestedField) {
new NestedClass().nestedField = nestedField;
}
}
由于外部类需要访问内部类的私有字段,编译器会生成合成方法来实现访问。否则,从外部类访问私有字段将不可行。
通过反射,我们可以看到编译器生成了两个名为 access$0
和 access$1
的合成方法:
public void givenSyntheticMethod_whenIsSynthetic_thenTrue() {
Method[] methods = SyntheticMethodDemo.NestedClass.class
.getDeclaredMethods();
assertEquals("This class should contain only two methods",
2, methods.length);
for (Method m : methods) {
System.out.println("Method: " + m.getName() + ", isSynthetic: " +
m.isSynthetic());
assertTrue("All the methods of this class should be synthetic",
m.isSynthetic());
}
}
⚠️ 注意:只有在字段被实际访问时,编译器才会生成这些合成方法。否则,它们会被优化掉。这就是为什么我们在示例中添加了 getter 和 setter 方法。
4.1. 桥接方法
桥接方法(Bridge Methods)是合成方法的一种特殊情况,主要用于处理泛型的类型擦除问题。
举个例子,我们实现一个简单的 Comparator
:
public class BridgeMethodDemo implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
}
虽然在源代码中 compare()
方法的参数是 Integer
类型,但经过类型擦除后,在字节码中参数类型变成了 Object
。
为了处理类型转换,编译器会生成一个桥接方法:
public int compare(Object o1, Object o2) {
return compare((Integer) o1, (Integer) o2);
}
我们可以通过反射验证桥接方法的存在:
public void givenBridgeMethod_whenIsBridge_thenTrue() {
int syntheticMethods = 0;
Method[] methods = BridgeMethodDemo.class.getDeclaredMethods();
for (Method m : methods) {
System.out.println("Method: " + m.getName() + ", isSynthetic: " +
m.isSynthetic() + ", isBridge: " + m.isBridge());
if (m.isSynthetic()) {
syntheticMethods++;
assertTrue("The synthetic method in this class should also be a bridge method",
m.isBridge());
}
}
assertEquals("There should be exactly 1 synthetic bridge method in this class",
1, syntheticMethods);
}
5. 合成构造器
再来看一个内部类使用私有构造器的例子:
public class SyntheticConstructorDemo {
private NestedClass nestedClass = new NestedClass();
class NestedClass {
private NestedClass() {}
}
}
在这种情况下,为了允许外部类实例化这个私有构造器的内部类,编译器会生成一个合成构造器。
我们可以用如下测试代码验证:
public void givenSyntheticConstructor_whenIsSynthetic_thenTrue() {
int syntheticConstructors = 0;
Constructor<?>[] constructors = SyntheticConstructorDemo.NestedClass
.class.getDeclaredConstructors();
assertEquals("This class should contain only two constructors",
2, constructors.length);
for (Constructor<?> c : constructors) {
System.out.println("Constructor: " + c.getName() +
", isSynthetic: " + c.isSynthetic());
if (c.isSynthetic()) {
syntheticConstructors++;
}
}
assertEquals(1, syntheticConstructors);
}
和合成字段类似,这个合成构造器是实现从外部类实例化私有构造器内部类的关键。
⚠️ 同样地,从 JDK 11 开始,这类合成构造器也不再生成。
6. 总结
本文介绍了 Java 编译器生成的几种合成构造:合成字段、合成方法(包括桥接方法)和合成构造器。它们的存在是为了在编译期解决访问权限和类型擦除带来的问题。
我们通过反射机制验证了这些合成构造的存在,如果你对反射还不熟悉,可以参考这篇文章:Java 反射详解。
✅ 所有示例代码均可在 GitHub 获取:Baeldung GitHub 仓库。