Java 11ネストベースのアクセス制御

1. 前書き

このチュートリアルでは、Java 11で導入された新しいアクセス制御コンテキストである_nests_について説明します。

*2. Java 11 *より前

2.1. 入れ子になった型

Javaでは、クラスとインターフェースをlink:/java-nested-classes [相互にネスト]にすることができます。 これらの*ネストされた型は、プライベートフィールド、メソッド、コンストラクターなど、相互に無制限にアクセスできます*。
次のネストされたクラスの例を考えてみましょう。
public class Outer {

    public void outerPublic() {
    }

    private void outerPrivate() {
    }

    class Inner {

        public void innerPublic() {
            outerPrivate();
        }
    }
}
ここでは、メソッド_outerPrivate()_は_private_ですが、メソッド_innerPublic()_からアクセスできます。
*最上位の型に加えて、その中にネストされたすべての型を、ネストを形成するものとして説明できます。 ネストの2つのメンバーはネストメイトとして記述されます。*
したがって、上記の例では、_Outer_と_Inner_が一緒にネストを形成し、互いにネストメイトになっています。

2.2. ブリッジ法

JVMアクセスルールは、ネストメイト間のプライベートアクセスを許可しません。 理想的には、上記の例でコンパイルエラーが発生するはずです。 ただし、Javaソースコードコンパイラは、間接性のレベルを導入することでアクセスを許可します。
たとえば、*プライベートメンバの呼び出しは、ターゲットクラスのコンパイラ生成のパッケージプライベートブリッジングメソッドの呼び出しにコンパイルされ、目的のプライベートメソッドを呼び出します*。
これはバックグラウンドで発生します。 このブリッジ方法は、デプロイされたアプリケーションのサイズをわずかに増加させ、ユーザーとツールを混乱させる可能性があります。

2.3. リフレクションを使用する

これのさらなる結果は、コアリフレクションもアクセスを拒否することです。 これは、リフレクション呼び出しがソースレベル呼び出しと同じように動作する必要があることを考えると、驚くべきことです。
たとえば、_Inner_クラスから反射的に_outerPrivate()_を呼び出そうとした場合:
public void innerPublicReflection(Outer ob) throws Exception {
    Method method = ob.getClass().getDeclaredMethod("outerPrivate");
    method.invoke(ob);
}
例外が発生します:
java.lang.IllegalAccessException:
Class com.baeldung.Outer$Inner can not access a member of class com.baeldung.Outer with modifiers "private"
Java 11はこれらの懸念に対処しようとします。

3. ネストベースのアクセス制御

  • Java 11は、ネストメイトの概念とJVM内の関連するアクセスルールをもたらします*。 これにより、Javaソースコードコンパイラのジョブが簡素化されます。

    これを実現するために、クラスファイル形式には2つの新しい属性が含まれるようになりました。
    1. 1つのネストメンバー(通常は最上位クラス)は、
      ネストホスト。 他の静的に既知のネストメンバーを識別するための属性(NestMembers)が含まれています。

    2. 他の各ネストメンバーには、識別するための属性(NestHost)があります。
      ネストホスト。

      したがって、タイプ_C_および_D_がネストメイトになるには、同じネストホストを持っている必要があります。 タイプ_C_は、NestHost属性に_D_がリストされている場合、_D_によってホストされているネストのメンバーであると主張します。 _D_がNestMembers属性に_C_もリストしている場合、メンバーシップが検証されます。 また、タイプ_D_は暗黙的にホストするネストのメンバーです。
      *コンパイラーがブリッジメソッドを生成する必要はありません*。
      最後に、ネストベースのアクセス制御は、コアリフレクションから驚くべき動作を取り除きます。 したがって、前のセクションで示した_innerPublicReflection()_メソッドは例外なく実行されます。

*4. Nestmate Reflection API *

  • Java 11は、コアリフレクションを使用して新しいクラスファイル属性を照会する手段を提供します*。 クラス_java.lang.Class_には、次の3つの新しいメソッドが含まれています。

4.1. getNestHost()

これは、この_Class_オブジェクトが属するネストのネストホストを返します。
@Test
public void whenGetNestHostFromOuter_thenGetNestHost() {
    is(Outer.class.getNestHost().getName()).equals("com.baeldung.Outer");
}

@Test
public void whenGetNestHostFromInner_thenGetNestHost() {
    is(Outer.Inner.class.getNestHost().getName()).equals("com.baeldung.Outer");
}
_Outer_クラスと_Inner_クラスは両方とも、ネストホスト_com.baeldung.Outer_に属します。

4.2. isNestmateOf()

これは、指定された_Class_がこの_Class_オブジェクトのネストメンバーであるかどうかを決定します。
@Test
public void whenCheckNestmatesForNestedClasses_thenGetTrue() {
    is(Outer.Inner.class.isNestmateOf(Outer.class)).equals(true);
}

4.3。 getNestMembers()

これは、この_Class_オブジェクトが属するネストのすべてのメンバーを表す_Class_オブジェクトを含む配列を返します。
@Test
public void whenGetNestMembersForNestedClasses_thenGetAllNestedClasses() {
    Set<String> nestMembers = Arrays.stream(Outer.Inner.class.getNestMembers())
      .map(Class::getName)
      .collect(Collectors.toSet());

    is(nestMembers.size()).equals(2);

    assertTrue(nestMembers.contains("com.baeldung.Outer"));
    assertTrue(nestMembers.contains("com.baeldung.Outer$Inner"));
}

5. コンパイルの詳細

5.1. Java 11より前のブリッジメソッド

コンパイラが生成したブリッジング方法の詳細を掘り下げましょう。 結果のクラスファイルを逆アセンブルすると、これを確認できます。
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
  public com.baeldung.Outer();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method java/lang/Object."<init>":()V
       4: return

  public void outerPublic();
    Code:
       0: return

  static void access$000(com.baeldung.Outer);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method outerPrivate:()V
       4: return
}
ここでは、デフォルトのコンストラクタとパブリックメソッド_outerPublic()_を除いて、*メソッド_access $ 000()_に注意してください。 コンパイラは、これをブリッジングメソッドとして生成します。*
_innerPublic()_はこのメソッドを経由して_outerPrivate()_を呼び出します。
$ javap -c Outer\$Inner
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
  final com.baeldung.Outer this$0;

  com.baeldung.Outer$Inner(com.baeldung.Outer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/baeldung/Outer;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void innerPublic();
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Lcom/baeldung/Outer;
       4: invokestatic  #3                  // Method com/baeldung/Outer.access$000:(Lcom/baeldung/Outer;)V
       7: return
}
19行目のコメントに注目してください。 ここで、_innerPublic()_はブリッジメソッド_access $ 000()_を呼び出します。

5.2. Java 11のネストメイト

Java 11コンパイラは、次の逆アセンブルされた_Outer_クラスファイルを生成します。
$ javap -c Outer
Compiled from "Outer.java"
public class com.baeldung.Outer {
  public com.baeldung.Outer();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void outerPublic();
    Code:
       0: return
}
コンパイラによって生成されたブリッジングメソッドがないことに注意してください。 また、_Inner_クラスは_outerPrivate()_メソッドを直接呼び出すことができるようになりました。
$ javap -c Outer\$Inner.class
Compiled from "Outer.java"
class com.baeldung.Outer$Inner {
  final com.baeldung.Outer this$0;

  com.baeldung.Outer$Inner(com.baeldung.Outer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/baeldung/Outer;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return

  public void innerPublic();
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Lcom/baeldung/Outer;
       4: invokevirtual #3                  // Method com/baeldung/Outer.outerPrivate:()V
       7: return
}

6. 結論

この記事では、Java 11で導入されたネストベースのアクセス制御について説明しました。
通常どおり、コードスニペットはhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-11[GitHubで]にあります。