1概要

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


2 Javaで合成

私たちがおそらく見つけることができる

synthetic

の最良の定義は直接Java言語仕様(https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html[JLS 13.1.7])から来ています。

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

さまざまな種類のコンパイル構成体、つまりフィールド、コンストラクター、およびメソッドがあります。一方、

入れ子になったクラスはコンパイラによって変更される可能性があります(つまり、無名クラス)が、合成

とは見なされません** 。

さらに苦労することなく、それぞれについて深く掘り下げましょう。


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 $ 0



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

  • コードを生成するには、フィールドは実際に

    から読み書きされる必要があります。それ以外の場合、メソッドは

    最適化されます。これが、ゲッターとセッターも追加した理由です。

** 4.1. ブリッジ方法

合成メソッドの特別なケースは、ジェネリックの型消去を扱うブリッジメソッドです。

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

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

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


6. 結論

この記事では、Javaコンパイラーによって生成される合成構文について説明しました。それらをテストするために、我々はあなたがhttps://www.baeldung.com/java-reflection[here]についてもっと学ぶことができる反射を利用しました。

いつものように、すべてのコードが利用可能ですhttps://github.com/eugenp/tutorials/tree/master/core-java-lang[GitHubでオーバー]]。