Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Java 11 / JEP 181のNestmatesでのバイトコードの変更を見比べる

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メソッドを経由するバイトコードとなっていました。

詳細はこのブログに一度書いていますので、前提知識として読んでいただければと思います。

www.sakatakoichi.com

参考セッション動画

JVMLS 2018でこのJEPの担当者であるKaren KinnearさんによるNestmatesのセッションがありました。私は現地にて聴きました。録画も公開されています。

youtu.be

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でクラスファイルに新しい属性NestHostNestMembersというものが追加されました。

$ 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で今まで少し奇妙だった部分が解消されました!