Javaの合成コンストラクト

Javaの合成コンストラクト

1. 概要

このチュートリアルでは、Javaの合成構造、つまり、可視性が不十分であるか参照が欠落しているために到達できないメンバーへのアクセスを透過的に処理するためにコンパイラによって導入されたコードについて説明します。

2. Javaで合成

おそらく見つけることができるsyntheticの最良の定義は、Java言語仕様(JLS 13.1.7)から直接得られます。

Javaコンパイラーによって導入された、ソースコードに対応するコンストラクトがないコンストラクトは、デフォルトのコンストラクター、クラス初期化メソッド、およびEnumクラスの値とvalueOfメソッドを除いて、合成としてマークする必要があります。

さまざまな種類のコンパイル構成体、つまりフィールド、コンストラクター、およびメソッドがあります。 一方、although nested classes can be altered by the compiler (i.e. anonymous classes), they aren’t considered synthetic

さらに面倒なことはせずに、これらのそれぞれについて深く掘り下げましょう。

3. 合成フィールド

単純なネストされたクラスから始めましょう。

public class SyntheticFieldDemo {
    class NestedClass {}
}

コンパイル時、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.

これが起こっていることを確認するために、ネストされたクラスフィールドをリフレクションによって取得し、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());
    }
}

これを確認するもう1つの方法は、コマンド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;
    }
}

この場合、the compilation will generate accessors to the variable. Without these methods, it’d be impossible to access a private field from the enclosing instance.

繰り返しますが、access$0access$1と呼ばれる2つの合成メソッドを示す同じ手法でこれを確認できます。

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

in order to generate the code, the field must actually be read from or written tootherwise, the methods will be optimized awayに注意してください。 これが、ゲッターとセッターも追加した理由です。

4.1. ブリッジメソッド

合成メソッドの特殊なケースは、ジェネリックの型消去を処理するブリッジメソッドです。

たとえば、単純な Comparatorについて考えてみましょう。

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

compare()はソースで2つのInteger引数を取りますが、コンパイルされると、型消去のために、代わりに2つのObject引数を取ります。

これを管理するには、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);
}

以前のテストに加えて、今回は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. 合成コンストラクター

最後に、プライベートコンストラクターを追加します。

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

    class NestedClass {
        private NestedClass() {}
    }
}

今回は、テストまたは逆アセンブラを実行すると、実際には2つのコンストラクタがあり、そのうちの1つは合成であることがわかります。

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

合成フィールドと同様に、this generated constructor is essential to instantiate a nested class with a private constructor from its enclosing instance.

6. 結論

この記事では、Javaコンパイラーによって生成される合成コンストラクトについて説明しました。 それらをテストするために、リフレクションを使用しました。これにより、hereについて詳しく知ることができます。

いつものように、すべてのコードはover on GitHubで利用できます。