1. 概要

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

注: JDK 11 以降、合成メソッドとコンストラクターはネストベースのアクセス制御に置き換えられたため、生成されなくなりました。

2. Javaで合成

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

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

コンパイル構造には、フィールド、コンストラクター、メソッドなど、さまざまな種類があります。 一方で、 ただし、ネストされたクラスはコンパイラによって変更できます(つまり、 匿名クラス)、それらは合成とは見なされません

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

3. 合成フィールド

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

public class SyntheticFieldDemo {
    class NestedClass {}
}

コンパイルすると、 内部クラスには、最上位クラスを参照する合成フィールドが含まれます。 偶然にも、これにより、ネストされたクラスからそれを囲むクラスメンバーにアクセスできるようになります。

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

この場合、 コンパイルにより、変数へのアクセサーが生成されます。 これらのメソッドがないと、囲んでいるインスタンスからプライベートフィールドにアクセスすることはできません。

もう一度、 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());
    }
}

コードを生成するには、フィールドを実際に、から読み取るか、に書き込む必要があることに注意してください。そうでない場合、メソッドは最適化されます。 これが、ゲッターとセッターも追加した理由です。

上記のように、これらの合成メソッドは、JDK11以降では生成されなくなりました。

4.1. ブリッジメソッド

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

たとえば、単純なコンパレータについて考えてみましょう。

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

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

これを管理するために、コンパイラは、引数のキャストを処理する合成ブリッジを作成します

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

合成フィールドと同様に、この生成されたコンストラクターは、ネストされたクラスを、それを囲むインスタンスからのプライベートコンストラクターでインスタンス化するために不可欠です。

前述のように、JDK 11以降、合成コンストラクターは生成されなくなりました。

6. 結論

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

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