Fight the Future

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

Project ValhallaのL-World Value Typesをちょっと試す

Project ValhallaのL-World Value Typesのアーリーアクセスビルドが出ています。

Valhalla Early-Access Builds

Value Typesは、L-WorldではないQタイプのMinimal Value Types(MVT)という初期のプロトタイプがありました。

www.infoq.com

現在は従来のLタイプを使ったプロトタイプ、すなわちL-WorldのValue Typesです。

JVM Language Summit 2018のセッションで詳しく解説されています。

youtu.be

今回は、このセッションの内容をそのままなぞってValue Typesを試します。

準備

Build 0 (2018/7/30)を使います。自分でビルドする必要はなくダウンロードして解凍するだけで使えます。JAVA_HOMEも設定しましょう。

$ java -version
openjdk version "11-lworldea" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11-lworldea+0-2018-07-30-1734349.david.simms.valhalla)
OpenJDK 64-Bit Server VM 18.9 (build 11-lworldea+0-2018-07-30-1734349.david.simms.valhalla, mixed mode)

Value Typesを作ります。

__ByValue class MyValue {
    private int id;

    MyValue() {
        this.id = -1;
    }

    MyValue(int id) {
        this.id = id;
    }
}

Value Typesには__ByValueをつけるのですが、これはまだプロトタイプだからです。今後正式なキーワードが決まるでしょう。

Value Typesはイミュータブルですが、フィールドにfinalを自分でつける必要はありません。

コンパイルしたものをjavapします。

$ javap -p MyValue
Compiled from "MyValue.java"
final value class MyValue {
  private final int id;
  MyValue();
  MyValue(int);
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public final java.lang.String toString();
  public final long longHashCode();
  static MyValue $makeValue$();
  static MyValue $makeValue$(int);
}

フィールドidにfinalがついているのがわかります。クラスもfinalクラスとなります。hashCodeやequals、toStringメソッドも生成されていますが、$makeValue$というstaticメソッドも作られています。これを見てみましょう。

$ javap -c MyValue
Compiled from "MyValue.java"
final value class MyValue {
// *snip*
  static MyValue $makeValue$();
    Code:
       0: defaultvalue  #6                  // class MyValue
       3: astore_0
       4: iconst_m1
       5: aload_0
       6: swap
       7: withfield     #7                  // Field id:I
      10: astore_0
      11: aload_0
      12: areturn

  static MyValue $makeValue$(int);
    Code:
       0: defaultvalue  #6                  // class MyValue
       3: astore_1
       4: iload_0
       5: aload_1
       6: swap
       7: withfield     #7                  // Field id:I
      10: astore_1
      11: aload_1
      12: areturn
}

今までにないバイトコードdefaultvaluewithfieldがあります。これはValue Typesで新たに導入されるバイトコードです。defaultvalueはデフォルト値のValue Typeインスタンスを作ります。Value Typeのフィールドで、プリミティブはそれぞれのデフォルト値で初期化されます。あとで試しましょう。withfieldはValue Typeのインスタンスからフィールドの値を更新した新しいValue Typeのインスタンスを作ります。なぜなら、Value Typesはイミュータブルだからです。

Value Typesであることもクラスファイルに書き込まれます。

$ javap -v MyValue
...
final value class MyValue
  minor version: 0
  major version: 55
  flags: (0x0130) ACC_FINAL, ACC_SUPER, ACC_VALUE
...
Constant pool:
...
   #6 = Class              #25            // MyValue
...
ValueTypes:
  #6;                                     // value class MyValue

ValueTypesというセクションが新しくできています。そしてACC_VALUEというフラグが付いています。

実行

Value Typesを利用してみましょう。

public class Main {
    public static void main(String[] args) {
        MyValue[] array = new MyValue[10];
        System.out.println(array[1]);
        
        MyValue v1 = new MyValue();
        MyValue v2 = new MyValue(100);

        System.out.println(v1);
        System.out.println(v2);
    }
}
$ javac Main.java
$ java Main
Exception in thread "main" java.lang.ClassFormatError: Class modifier ACC_VALUE in class MyValue requires option -XX:+EnableValhalla
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:994)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:801)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:699)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:622)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)
        at Main.main(Main.java:3)

このアーリーアクセスビルドでは、Value Typesを使うには-XX:+EnableValhallaオプションが必要です。付けます。

$ java -XX:+EnableValhalla Main
[value class MyValue, 0]
[value class MyValue, -1]
[value class MyValue, 100]

toStringメソッドは自動的に生成されているので、値が出力されました。あれ!?と思いませんか?

        MyValue[] array = new MyValue[10];
        System.out.println(array[1]);

配列を生成しただけでインスタンスを代入していないのに、0の値を持つValue Typeが出力されています。さきほどの説明にもあった、デフォルト値のValue Typeが生成されるためです。たとえばValue Typesでない、今までの参照型ならこうですよね。

        String[] s = new String[10];
        System.out.println(s[1]); // null

Value Typesはnullで処理できないためです。もちろんこう書けてしまいますが…

        MyValue[] array = new MyValue[10];
        System.out.println(array[1]);
        
        Object[] objects = array;
        objects[0] = null;

現在の実装では実行時にNullPointerExceptionとなります。

$ java -XX:+EnableValhalla Main
Exception in thread "main" java.lang.NullPointerException
        at Main.main(Main.java:13)

実験

Value Typeのフィールドをプリミティブのintから参照型のStringに変えるとどうなるのでしょう。

__ByValue class MyValue {
    private String id;

    MyValue() {
        this.id = "-1";
    }

    MyValue(String id) {
        this.id = id;
    }
}

public class Main {
    public static void main(String[] args) {
        MyValue[] array = new MyValue[10];
        System.out.println(array[1]);
    }
}

NullPointerExceptionでした。

$ java -XX:+EnableValhalla Main
Exception in thread "main" java.lang.NullPointerException
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
        at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
        at java.base/java.lang.invoke.ValueBootstrapMethods.toString(ValueBootstrapMethods.java:236)
        at MyValue.toString(MyValue.java:1)
        at java.base/java.lang.String.valueOf(String.java:2949)
        at java.base/java.io.PrintStream.println(PrintStream.java:897)
        at Main.main(Main.java:4

これは、ValueTypesのフィールドが参照型だとデフォルト値はnullなので、自動的に生成されたtoStringメソッドがnullをうまく扱えていないだけですね。toStringやequalsの生成処理はほぼ未着手だそうです(indyは使っていますが)。

以下のようにすると動作します。

public class Main {
    public static void main(String[] args) {
        MyValue v1 = new MyValue();
        MyValue v2 = new MyValue("100");

        System.out.println(v1);
        System.out.println(v2);
    }
}
$ java -XX:+EnableValhalla Main
[value class MyValue, -1]
[value class MyValue, 100]