JEP 181: Nest-Based Access Controlというものがあります。これはNestmates(ネストメイト)と呼ばれるものです。
http://openjdk.java.net/jeps/181
Nestmatesは、クラスやインタフェースをネストして作成したとき、ネストする側される側の両方を指す言葉です。アウタークラスとインナークラスといったものです。
実はJava 10まで、インナークラスからアウタークラスのprivateメソッドを呼び出すコードを書くと、変わったバイトコード出力となっていました。こういったコードです。
public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
このとき、Outerクラスのouterpriv()
メソッドの呼び出しが、access$000
というstaticメソッドを経由するバイトコードとなっていました。
詳細はこのブログに一度書いていますので、前提知識として読んでいただければと思います。
参考セッション動画
JVMLS 2018でこのJEPの担当者であるKaren KinnearさんによるNestmatesのセッションがありました。私は現地にて聴きました。録画も公開されています。
Java 11とそれ以前のバイトコードを比べる
以前のバイトコード
$ javap -p -c Outer\$Inner Compiled from "Outer.java" class Outer$Inner { final Outer this$0; Outer$Inner(Outer); Code: 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LOuter; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return public void test(); Code: 0: new #3 // class Outer 3: dup 4: invokespecial #4 // Method Outer."<init>":()V 7: invokestatic #5 // Method Outer.access$000:(LOuter;)V 10: return }
InnerクラスはOuterのstaticメソッドaccess$000
を呼び出します。
$ javap -p -c Outer Compiled from "Outer.java" public class Outer { public Outer(); Code: 0: aload_0 1: invokespecial #2 // Method java/lang/Object."<init>":()V 4: return private void m_outerpriv(); Code: 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String called m_outerpriv 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return ... static void access$000(Outer); Code: 0: aload_0 1: invokespecial #1 // Method m_outerpriv:()V 4: return }
Outerクラスにはaccess$000
が合成メソッドとしてコンパイラによって作られています。access$000
メソッドから本来呼び出したかったm_outerpriv
メソッドを呼び出しています。
Java 11
$ javap -p -c Outer\$Inner Compiled from "Outer.java" class Outer$Inner { final Outer this$0; Outer$Inner(Outer); Code: 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:LOuter; 5: aload_0 6: invokespecial #2 // Method java/lang/Object."<init>":()V 9: return public void test(); Code: 0: new #3 // class Outer 3: dup 4: invokespecial #4 // Method Outer."<init>":()V 7: invokevirtual #5 // Method Outer.m_outerpriv:()V 10: return }
Innerクラスはaccess$000
メソッドではなくOuterクラスのm_outerpriv
メソッドを直接呼び出しています。
$ javap -p -c Outer Compiled from "Outer.java" public class Outer { public Outer(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return private void m_outerpriv(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String called m_outerpriv 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void main(java.lang.String[]); Code: 0: new #5 // class Outer$Inner 3: dup 4: new #6 // class Outer 7: dup 8: invokespecial #7 // Method "<init>":()V 11: invokespecial #8 // Method Outer$Inner."<init>":(LOuter;)V 14: invokevirtual #9 // Method Outer$Inner.test:()V 17: return }
Outerクラスにaccess$000
メソッドがありません!
仕組み
このJEP 181でクラスファイルに新しい属性NestHost
とNestMembers
というものが追加されました。
$ javap -v Outer ... NestMembers: Outer$Inner $ javap -v Outer\$Inner ... NestHost: class Outer
ネストする側はNestMembers
属性を持ち、ネストされる側はNestHost
属性を持って、関係を表現しています。この属性を使って、インナークラスからアウタークラスのprivateメソッドの実行が許可されるという感じでしょう。
たったこれだけの小さなことですが、さまざまな変更が必要です。コンパイラの変更はもちろん、JVM仕様も以下のように変更される予定です。
Specification for JEP 181: Nest-based Access Control
アクセス制御の部分はもちろん、クラスファイルの属性、リフレクションAPIなどです。たとえばClassクラス。
getNestHost
メソッドgetNestMembers
メソッドisNestmateOf
メソッド
こういったメソッドを追加しています。
まとめ
使う側には影響を及ぼしません(もちろん意図して及ぼさないようにされています)が、中にはこうした変更もJava/JVMにはあります。Nestmatesで今まで少し奇妙だった部分が解消されました!