Project ValhallaのL-World Value Typesのアーリーアクセスビルドが出ています。
Value Typesは、L-WorldではないQタイプのMinimal Value Types(MVT)という初期のプロトタイプがありました。
現在は従来のLタイプを使ったプロトタイプ、すなわちL-WorldのValue Typesです。
JVM Language Summit 2018のセッションで詳しく解説されています。
今回は、このセッションの内容をそのままなぞって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 }
今までにないバイトコードdefaultvalue
とwithfield
があります。これは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]