Synthetic Constructs in Java – Java中的合成结构

最后修改: 2018年 9月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll take a look at Java’s synthetic constructs, code introduced by the compiler to transparently handle access to members which would be otherwise unreachable due to insufficient visibility or missing references.

在本教程中,我们将看看Java的合成结构,这些代码由编译器引入,用于透明地处理对成员的访问,否则这些成员将因可见性不足或缺少引用而无法访问。

Note: starting with JDK 11, synthetic methods and constructors are no longer generated, as they’re superseded by nest-based access control.

注意:从JDK 11开始,不再生成合成方法和构造函数,因为它们被基于巢穴的访问控制所取代了。

2. Synthetic in Java

2.Java中的合成

The best definition of synthetic we could possibly find comes directly from the Java Language Specification (JLS 13.1.7):

我们所能找到的关于synthetic的最佳定义直接来自于《Java语言规范》(JLS 13.1.7)。

Any constructs introduced by a Java compiler that do not have a corresponding construct in the source code must be marked as synthetic, except for default constructors, the class initialization method, and the values and valueOf methods of the Enum class.

由Java编译器引入的任何构造,如果在源代码中没有相应的构造,就必须被标记为合成的,但默认构造器、类的初始化方法以及Enum类的value和valueOf方法除外。

There are different kinds of compilation constructs, namely fields, constructors, and methods. On the other hand, although nested classes can be altered by the compiler (i.e. anonymous classes), they aren’t considered synthetic.

有不同种类的编译构造,即字段、构造函数和方法。另一方面,虽然嵌套类可以被编译器改变(即匿名类),但它们不被视为合成的

Without further ado, let’s delve deep into each of these.

闲话少说,让我们逐一深入了解。

3. Synthetic Fields

3.人工合成场

Let’s begin with a simple nested class:

让我们从一个简单的嵌套类开始。

public class SyntheticFieldDemo {
    class NestedClass {}
}

When compiled, any inner class will contain a synthetic field which references the top level class. Coincidentally, this is what makes possible to access the enclosing class members from a nested class.

在编译时,任何内部类都将包含一个合成字段,它引用了顶层类。巧合的是,这使得从一个嵌套类中访问包围类的成员成为可能。

To make sure that this is what’s happening, we’ll implement a test which gets the nested class fields by reflection and checks them using the isSynthetic() method:

为了确保这一点,我们将实现一个测试,通过反射获得嵌套类的字段,并使用isSynthetic()方法检查它们。

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

Another way we could verify this would be by running the disassembler through the command javap. In either case, the output shows a synthetic field named this$0.

另一种方法是,我们可以通过javap命令运行反汇编程序来验证这一点。在这两种情况下,输出显示了一个名为this$0.的合成字段。

4. Synthetic Methods

4.人工合成方法

Next up, we’ll add a private field to our nested class:

接下来,我们要给我们的嵌套类添加一个私有字段。

public class SyntheticMethodDemo {
    class NestedClass {
        private String nestedField;
    }

    public String getNestedField() {
        return new NestedClass().nestedField;
    }

    public void setNestedField(String nestedField) {
        new NestedClass().nestedField = nestedField;
    }
}

In this case, the compilation will generate accessors to the variable. Without these methods, it’d be impossible to access a private field from the enclosing instance.

在这种情况下,编译将生成该变量的访问器。如果没有这些方法,就不可能从包围的实例中访问一个私有字段。

Once again, we can check this with the same technique which shows two synthetic methods called access$0 and access$1:

再一次,我们可以用同样的技术来检查这一点,它显示了两个名为access$0access$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());
    }
}

Notice that in order to generate the code, the field must actually be read from or written to, otherwise, the methods will be optimized away. This is the reason why we also added a getter and a setter.

请注意,为了生成代码,字段必须实际被读出或写入否则,这些方法将被优化掉。这就是为什么我们还添加了一个getter和一个setter的原因。

As mentioned above, these synthetic methods are no longer generated starting with JDK 11.

如上所述,从JDK 11开始,这些合成方法不再被生成。

4.1. Bridge Methods

4.1.桥梁方法

A special case of synthetic methods is bridge methods, which handle type-erasure of generics.

合成方法的一个特例是桥梁方法,它处理泛型的类型消除。

For instance, let’s consider a simple Comparator:

例如,让我们考虑一个简单的比较器

public class BridgeMethodDemo implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
}

Although compare() takes two Integer arguments in the source, once compiled it’ll take two Object arguments instead, due to type erasure.

尽管compare()在源码中需要两个Integer参数,但由于类型擦除,一旦编译,它将需要两个Object参数来代替。

To manage this, the compiler creates a synthetic bridge which takes care of casting the arguments:

为了处理这个问题,编译器创建了一个合成桥,负责铸造参数

public int compare(Object o1, Object o2) {
    return compare((Integer) o1, (Integer) o2);
}

In addition to our previous tests, this time we’ll also call isBridge() from the Method class:

除了之前的测试外,这次我们还将从Method类中调用isBridge()

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. Synthetic Constructors

5.合成构造器

Finally, we’ll add in a private constructor:

最后,我们将添加一个私有构造函数。

public class SyntheticConstructorDemo {
    private NestedClass nestedClass = new NestedClass();

    class NestedClass {
        private NestedClass() {}
    }
}

This time, once we run the test or the disassembler we’ll see that there are actually two constructors, one of which is synthetic:

这一次,一旦我们运行测试或反汇编程序,就会发现实际上有两个构造函数,其中一个是合成的。

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

Similarly to the synthetic fields, this generated constructor is essential to instantiate a nested class with a private constructor from its enclosing instance.

与合成字段类似,这个生成的构造函数对于从其包围的实例中用私有构造函数实例化一个嵌套类是必不可少的。

As mentioned above, the synthetic constructor is no longer generated starting with JDK 11.

如上所述,从JDK 11开始不再生成合成构造函数。

6. Conclusion

6.结论

In this article, we discussed synthetic constructs generated by the Java compiler. To test them, we made use of reflection, which you can learn more about here.

在这篇文章中,我们讨论了由Java编译器生成的合成结构。为了测试它们,我们使用了反射,你可以在这里了解更多信息

As always, all the code is available over on GitHub.

像往常一样,所有的代码都可以在GitHub上找到