Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Head toward Java 22 and Java 23

Head toward Java 22 and Java 23

JJUGナイトセミナー「Java 22 リリース記念イベント」での発表資料です。発表後にフィードバックを元に表現を一部加筆しております。

https://jjug.doorkeeper.jp/events/169972

More Decks by LINEヤフーTech (LY Corporation Tech)

Other Decks in Technology

Transcript

  1. Head toward Java 22 (and Java 23) KUBOTA Yuji LINEヤフー株式会社

    Japan Java User Group Night Seminar 2024/03/27
  2. KUBOTA Yuji (@sugarlife) Manager at LINEヤフー株式会社, IMF team IMF team

    = Kafka infrastructure team エンジニアリングの究極的な挑戦の場はここにある Kafkaイン フラを全社に提供するIMFチームの仕事のやりがい: https://logmi.jp/tech/articles/325945 「膨大な量のトラフィックを扱えるLINEの環境は魅力的」 Kafkaスペシャリストが語る仕事の醍醐味: https://engineering.linecorp.com/en/interview/kafka-okada 2
  3. Recent updates JDK JEP Non-Standard JEP Remark 17 14 3

    LTS 18 9 3 - 19 7 6 - 20 7 7 - 21 15 7 LTS 22 12 8 - 23 1 1 In development 1: Incubator or Preview or Experimental 3
  4. 4

  5. JEPで導入される機能のフェーズ Incubator (JEP 11): Java APIの試験用モジュール( jdk.incubator )。標準化に向けてフィ ードバックを得て変更しやすいように特別なモジュールにしている。有効にするには --add-

    modules jdk.incubator.xxx の指定が必要 Preview (JEP 12): Java言語の試験機能。有効にするには --enable-preview の指定が必 要。コンパイル時には --source XX か --release XX の指定も必要( XX はバージョン) Experimental(試験機能): GCやコンパイラなどのランタイムの試験機能。有効にするには機 能ごとのオプション以外に、 -XX:+UnlockExperimentalVMOptions の指定が必要 Standard(標準機能): 上記の試験フェーズを通じて標準機能に昇格した機能。リリースサイク ルは6ヶ月だがフィードバック対応はおおよそ3ヶ月しかない(残り3ヶ月は安定化対応)ので、 おおよそ2回以上のバージョンアップを挟んで昇格することが多い 5
  6. JEPs in JDK 22 JEP 423: Region Pinning for G1

    JEP 447: Statements before super(...) (Preview) JEP 454: Foreign Function & Memory API JEP 456: Unnamed Variables & Patterns JEP 457: Class-File API (Preview) JEP 458: Launch Multi-File Source-Code Programs JEP 459: String Templates (Second Preview) JEP 460: Vector API (Seventh Incubator) JEP 461: Stream Gatherers (Preview) JEP 462: Structured Concurrency (Second Preview) JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview) JEP 464: Scoped Values (Second Preview) 6
  7. JEP in JDK 23 JEP 455: Primitive Types in Patterns,

    instanceof, and switch (Preview) 7
  8. JEP 423: Region Pinning for G1 JNIによってJava threadがcritical regionにいる間、GCが無効化される (GCLocker)

    GetXXXCritical --(GC停止)--> ReleaseXXXCritical (doc) GCLocker Initiated GC が起動する critical objectがいるリージョン(Region)をPinningする(Evacuationさせない)ことでGC を待たせることなく処理できるようになった ここでのリージョン = G1 GCにおけるヒープ管理の単位 (slide) Pinning自体はすでにHumongous ObjectやClass Data Sharingなどで導入済み OOME発生可能性の緩和とパフォーマンス改善(全体のstall回避)の効果がある Shenandoahは(おそらく当初から)対応済み。ZGCは"Open"状態 JDK-8269423 1: GCCauseとしてGCログに出力される: https://github.com/openjdk/jdk/blob/jdk- 23+15/src/hotspot/share/gc/shared/gcLocker.cpp#L165 8
  9. JEP 447: Statements before super(...) (Preview) 親クラスのコンストラクタ(以下 super() )呼び出し前に処理が書けるようになった 前提

    初期化前のフィールドのアクセスは禁じる必要がある 子クラスのフィールド初期値は親クラスのフィールドの初期値に依存することがあ るのでコンストラクタはトップダウンで呼び出される必要がある このため、子クラスのコンストラクタは super() が完了するまで、親クラスおよ び自クラスのフィールドにアクセスしてはならない トップダウンでの実行を保証するために、コンストラクタでは他コンストラクタの 明示的な呼び出しを最初にする制約がある この明示的な呼び出しを行う際に引数のどれもが現在のオブジェクト( this )にア クセスできないようにする制約がある 9
  10. super(...) の引数に対する処理を書きたい 例:値の検証 Before // Pattern 1: 素直に書く public class

    Child extends Parent { public Child(String name) { super(name); // 制約による親クラスのコンストラクタ呼び出し if (name.equals("Anakin")) throw new IllegalArgumentException("you're not my father"); } } 10
  11. super(...) の引数に対する処理を書きたい 例:値の検証 Before // Pattern 2: コンストラクタ呼び出し前に処理を実行させる public class

    Child extends Parent { public Child(String name) { super(verifyFather(name)); } // static で書く必要がある検証メソッド private static int verifyFather(String name) { if (name.equals("Anakin")) throw new IllegalArgumentException("you're not my father"); return name; } } 11
  12. super(...) の引数に対する処理を書きたい 例:値の検証 After public class Child extends Parent {

    public Child(String name) { if (name.equals("Anakin")) throw new IllegalArgumentException("you're not my father"); super(name); } } 12
  13. this(...) の引数に対する処理を書きたい 例:値の準備 After public record Name(String first, String last)

    { public Name(String fullName) { var names = fullName.split(" "); // 今まではこれがNG this(names[0], names[1]); } } var a = new Name("Yuji KUBOTA"); a ==> Name[first=Yuji, last=KUBOTA] 13
  14. super(...) の引数を共有したい Before public class Super { public Super(F f1,

    F f2) { ... } } public class Sub extends Super { private Sub(F f) { super(f, f); // 最初に呼ぶ必要があるのでこのコンストラクタを作る必要がある // 今回は `super(new F(), new F());` でも良いが2つ目は本来不要 } public Sub() { this(new F()); } } 14
  15. NG case - コンストラクト前のインスタンスはアクセス不可 class A { int i; A()

    { this.i++; // Error this.hashCode(); // Error System.out.print(this); // Error i++; // Error hashCode(); // Error super(); } } class B extends A { B() { super.i++; // Error super(); } } 16
  16. NG case - コンストラクト前のインスタンスはアクセス不可 class Outer { int outer; void

    hello() { System.out.println("Hello"); } Outer() { new Inner(); // Error - 'this' is enclosing instance super(); } class Inner { int inner; Inner() { Outer.this.outer++; // Allowed - enclosing instance Inner.this.inner++; // Error - same instance hello(); // Allowed - enclosing instance method super(); } } } 17
  17. $ javac --enable-preview --source 23 ConstructionTest.java ConstructionTest.java:7: error: cannot reference

    this before supertype constructor has been called this.i++; // Error ^ ConstructionTest.java:8: error: cannot reference this before supertype constructor has been called this.hashCode(); // Error ^ ConstructionTest.java:9: error: cannot reference this before supertype constructor has been called System.out.print(this); // Error ^ ConstructionTest.java:10: error: cannot reference i before supertype constructor has been called i++; // Error ^ ConstructionTest.java:11: error: cannot reference hashCode() before supertype constructor has been called hashCode(); // Error ^ ConstructionTest.java:18: error: cannot reference super before supertype constructor has been called super.i++; // Error ^ ConstructionTest.java:29: error: cannot reference this before supertype constructor has been called new Inner(); // Error - 'this' is enclosing instance ^ ConstructionTest.java:36: error: cannot reference this before supertype constructor has been called Inner.this.inner++; // Error - same instance 18
  18. Quiz class C<T> extends D { C() { super(this); //

    OK? } C(List<?> list) { super((T)list.get(0)); // OK? } } ※ list はどこから来たとかOut Of Boundsは今回スルー(コンパイル時にOKかどうか) 19
  19. Answer class C<T> extends D { C() { super(this); //

    Error - refers to 'this' } C(List<?> list) { super((T)list.get(0)); // Allowed - refers to 'T' but not 'this' } } this は初期化する前なのでNG。 T は型情報なのでOK 20
  20. JEP 456: Unnamed Variables & Patterns 不要な変数やパターンをJava 9で利用禁止になった _ で書けるようにし、不要なこと

    を明確にしたり不要なネストを排することで可読性・保守性を向上させる JDK 21から変更なしで標準機能化 名前の順序が変わってる JEP 443: Unnamed Patterns and Variables (Preview) 1: JDK-8061549: Disallow _ as a one-character identifier https://bugs.openjdk.org/browse/JDK-8061549 21
  21. Unnamed patterns (variable) OK // Before if (obj instanceof Point(int

    x, int y)) { System.out.println(y); /* x は不要*/ } // After if (obj instanceof Point(int _, int y)) { System.out.println(y); } if (obj instanceof Point(_, int y)) { System.out.println(y); } switch (o) { case Point(int x, _) -> ... } NG // Type o instanceof _ o instanceof _(int x, int y) case _ 22
  22. Unnamed local variables, exception param, lambda param ブロック内のローカル変数宣言文 (JLS 14.4.2)

    var _ = hoge.inplaceMethod() (※ forなどのブロック内) try-with-resourceのリソース指定 (JLS 14.20.3) try (var _ = ScopedContext.acquire()){ // 獲得したリソースは使わない } for文のヘッダ (JLS 14.14.1, JLS 14.14.2) for (int i=0, _=sideEffect(); i<10; i++){...} for (Integer _: int){...} catchブロックのexception引数 (JLS 14.20) catch (Throwable _) { /* ignore */ } ラムダ式のパラメータ (JLS 15.27.1) ...stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")) 23
  23. JEP 463: Implicitly Declared Classes and Instance Main Methods (Second

    Preview) クラス、パッケージ、モジュールといった概念はなしでただ実行できるようにして、プ ログラムの基本(変数、分岐、ルーチン等)から段階的に学べるようにする JDK21では「JEP 445: Unnamed Classes and Instance Main Method(Preview)」 「無名クラス」ではなく、ホストにより選択された名前のクラスに変更 もともとの目的は他のクラスによって使用されないようにするため 無名クラスではなく通常のクラスと同様の扱いにして実装を簡易化 main methodの選択プロセスを簡易化 (parameterありかなしか、staticかinstance かを考慮する必要があった) String[] があればそのメソッドを呼び出す、なければパラメータを持たない メソッド候補を呼び出す static methodとinstance methodを同じ名前とシグネチャで宣言することはで きないのでこれで十分 24
  24. Quiz どこまでOK? public static void main(String args[]) {} public static

    void main(String arg) {} public static void main() {} public void main(String args[]) {} protected void main(String args[]) {} private void main() {} void main() {} main() {} 25
  25. Answer error: implicitly declared class does not have main method

    in the form of void main() or void main(String[] args) public static void main(String args[]) {} public static void main(String arg) {} // NG: `String[]` は必須 public static void main() {} public void main(String args[]) {} protected void main(String args[]) {} private void main() {} // NG: private はアクセスできない void main() {} main() {} // NG: class, interface, enum, or record expected 26
  26. Before public class Main { public static void main(String[] args)

    { System.out.println("Hello, World!"); } } After void main() { System.out.println("Hello, World!"); } source-file modeで実行すればコンパイルすら要らない # ファイル名も何でもよい $ java --source 22 --enable-preview Test.java 27
  27. クラス宣言がない+main methodがインスタンスメソッド宣言時の扱われ方 void main() { System.out.println("Hello, World!"); } new Object()

    { void main() { System.out.println("Hello, World!"); } }.main(); JLS 15.9.5. Anonymous Class Declarations と相当する 28
  28. JEP 454: Foreign Function & Memory API 以下の既存APIの欠点を克服した「ヒープ外のメモリを直接扱うAPI」を提供 ByteBuffer でDirect

    Bufferは2GBまででメモリの開放はGCに依存する sun.misc.Unsafe はクラッシュの危険性があることと最終的には廃止させたい JNIはJavaで完結できずCコードを書く必要があり、CとJavaで行き来する必要があるの でオーバーヘッドが発生して性能が良くない 3回のPreviewを経て標準機能化。JAR-file manifestに Enable-Native-Access 追加などの 細かい修正あり 1: https://github.com/openjdk/jdk/commit/32ac72c3d35138f5253e4defc948304ac3ea1b53#diff- 56f51f9d0920f3600d9fffa99758c5066e8985a85ba6d28cbb321f6b6507ad34R721 2: https://github.com/openjdk/jdk/commit/32ac72c3d35138f5253e4defc948304ac3ea1b53 29
  29. Survey どれを使っていますか? 1. ByteBuffer#allocateDirect 2. sun.misc.Unsafe 3. JNI (Java Native

    Interface) 4. JNA (Java Native Access) 5. JNR (Java Native Runtime) 30
  30. puts(3) でHello, World!! public class FFMTest { public void puts(String

    message) { Linker linker = Linker.nativeLinker(); MethodHandle puts = linker.downcallHandle( linker.defaultLookup().find("puts").get(), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS) ); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocateFrom(STR."\{message}\n"); puts.invoke(segment); } catch (Throwable _) { /* ignore */ } } public static void main(String[] args) { new FFMTest().puts(args[0]); } } $ java --enable-preview --enable-native-access=ALL-UNNAMED FFMTest "Hello, World\!\!" Hello, World!! # 注: `--enable-preview` はSTR template のため 31
  31. 道標: Function と Memory public void puts(String message) { //

    Java とNative を相互運用を提供するリンク Linker linker = Linker.nativeLinker(); // Function MethodHandle puts = linker.downcallHandle( linker.defaultLookup().find("puts").get(), FunctionDescriptor.ofVoid(ADDRESS) ); // Memory try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocateFrom(STR."\{message}\n"); puts.invoke(segment); } catch (Throwable _) { /* ignore */ } } 32
  32. Lookup // Java コードからNative コードへの呼び出し (downcall) MethodHandle puts = linker.downcallHandle(

    // プラットフォームのライブラリ(commonly used library) から linker.defaultLookup().find("puts").get(), // System#load またはSystem#loadLibrary でロードされたライブラリから // SymbolLookup.loaderLookup().find("puts"), // 指定したライブラリから // SymbolLookup.libraryLookup(path_or_name, Arena), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS) ); NativeコードからJavaコードへの呼び出しは upcalls 。 Linker#upcallStub 33
  33. // System#load またはSystem#loadLibrary でロードされたライブラリから System.loadLibrary("GL"); // libGL.so SymbolLookup LibGL =

    SymbolLookup.loaderLookup(); MemorySegment glGetString = libGL.find("glGetString").orElseThrow(); // 指定したライブラリから try (Arena arena = Arena.ofConfined()) { SymbolLookup libGL = SymbolLookup.libraryLookup("libGL.so", arena); MemorySegment glGetString = libGL.find("glGetString").orElseThrow(); ... } 34
  34. Function (returning) pointer ValueLayout で表現する MethodHandle puts = linker.downcallHandle( linker.defaultLookup().find("puts").get(),

    // 戻り値がないのでofVoid, 引数はポインタ・配列: ADDRESS FunctionDescriptor.ofVoid(ValueLayout.ADDRESS) ); qsort の場合 ( ValueLayout は省略( import static ... )) void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); MethodHandle qsort = linker.downcallHandle( linker.defaultLookup().find("qsort").orElseThrow(), FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) ); 35
  35. 戻り値が int で可変長引数を持つ printf の場合 int printf(const char *format, ...);

    printf("%d plus %d equals %d", 2, 2, 4); // char*, int, int, int MethodHandle printf = linker.downcallHandle( linker.defaultLookup().find("printf").orElseThrow(), FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1) // 最初のint は可変長(0 はフォーマット文字列) ); try (Arena arena = Arena.ofConfined()) { int res = (int)printf.invokeExact( // print "2 plus 2 equals 4" arena.allocateFrom("%d plus %d equals %d"), 2, 2, 4 ); } 36
  36. 37

  37. 道標: Function と Memory public void puts(String message) { //

    Java とNative を相互運用を提供するリンク Linker linker = Linker.nativeLinker(); // Function MethodHandle puts = linker.downcallHandle( linker.defaultLookup().find("puts").get(), FunctionDescriptor.ofVoid(ADDRESS) ); // Memory try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocateFrom(STR."\{message}\n"); puts.invoke(segment); } catch (Throwable _) { /* ignore */ } } 38
  38. Arena 一つ以上の MemorySegment のライフサイクルをモデル化 try (Arena arena = Arena.ofConfined()) {

    MemorySegment segment = arena.allocateFrom(STR."\{message}\n"); puts.invoke(segment); } catch (Throwable _) { /* ignore */ } // segment deallocated 種類 ライフタイム マルチスレッドアクセス Global 無限 可 Auto(matic) GC 可 Confined ユーザが制御 不可 Shared ユーザが制御 可 39
  39. Arena Arenaが閉じられると全てのセグメントがアトミックに無効化される Shared arenaを閉じるとthread-local handshakeが起動する MemorySegment segment = null; try

    (Arena arena = Arena.ofConfined()) { segment = arena.allocateFrom(STR."\{message}\n"); puts.invoke(segment); } catch (Throwable _) { /* ignore */ } // segment deallocated segment.getString(0); // ==> illegalStateException: Already closed 40
  40. MemorySegment メモリ割り当て。heap segment(on-heap)とnative segment(off-heap)の2種類ある on-heapは MemorySegment#ofArray() factory methodで取得し、off-heapは Arena#allocate factory

    methodで取得する try (Arena arena = Arena.ofConfined()) { // off-heap にallocate して MemorySegment segment = arena.allocateFrom(STR."\{message}\n"); // kick puts.invoke(segment); } catch (Throwable _) { /* ignore */ } 41
  41. MemorySegment // int の場合 MemorySegment segment = arena.allocateFrom(JAVA_INT, 1); //

    アクセスも可能 int value = segment.get(JAVA_INT, 0); // 明示的にサイズを指定して確保し、Slicing も可能 MemorySegment segment = arena.allocate(100); // 100 bytes MemorySegment slice = segment.asSlice(50, 10); // offset:50, length:10 slice.get(JAVA_INT, 20); // read at offset:20 => Out of bounds 42
  42. MemorySegment (on-heap) final byte[] ba = new byte[5]; ba[0] =

    'h'; ba[1] = 'e'; ba[2] = 'l'; ba[3] = 'l'; ba[4] = 'o'; MemorySegment segment = MemorySegment.ofArray(ba); try { puts.invoke(segment); } catch (Throwable throwable) { throwable.printStackTrace(); } java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ heapBase: [B@442d9b6e, address: 0x0, byteSize: 100 } // * 注 at java.base/jdk.internal.foreign.abi.SharedUtils.checkNative(SharedUtils.java:320) at FFMTestOnHeap.puts(FFMTestOnHeap.java:26) at FFMTestOnHeap.main(FFMTestOnHeap.java:32) *注: 見易さのため改行を入れています 43
  43. その他 MemoryLayout : MemorySegmentの構造体表現 typedef struct { char kind; int

    value; } TaggedValues[5]; SequenceLayout TAGGED_VALUES = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout( JAVA_BYTE.withName("kind"), MemoryLayout.paddingLayout(3), JAVA_INT.withName("value") ) ).withName("TaggedValues"); VarHandle : 型付けされた変数への参照 Linker#upcallStub : 外部関数にポインタを渡すことができる jextract : ライブラリとヘッダファイルを元にグルーコードを作成するツール 44
  44. JEP 460: Vector API (Seventh Incubator) SIMD(Single Instruction, Multiple Data,

    複数データに対して同じ操作を同時に実行すること を可能にし、1つのCPUサイクルでより多くの計算を実行できる仕組み)演算をサポートする ベクトル演算を実行するためのJava API ベクトル演算を最適なハードウェアレジスタとベクトル命令にコンパイルするC2コンパ イラ 計算爆発しないように様々な演算(単項演算、二項演算、変換演算等)に対応する汎 用化された内部演算を定義 JDK 22では API をマイナー強化 + バグ修正 + 性能改善 MemorySegment は byte 配列のみVectorアクセスができたが、ほかのプリミティ ブもサポートするように拡張された 45
  45. 使用例: c = (a*a + b*b) (※ 行計算) import jdk.incubator.vector.FloatVector;

    // SPECIES: 一度に計算するbit 数 // PREFERRED = そのハードウェアで使える最大値 (_MAX もある) import static jdk.incubator.vector.FloatVector.SPECIES_PREFERRED; void vectorComputation(float[] a, float[] b, float[] c) { int upperBound = SPECIES_PREFERRED.loopBound(a.length); for (int i=0; i < upperBound; i += SPECIES_PREFERRED.length()) { var va = FloatVector.fromArray(SPECIES_PREFERRED, a, i); var vb = FloatVector.fromArray(SPECIES_PREFERRED, b, i); var vc = va.mul(va).add(vb.mul(vb)); vc.intoArray(c, i); } } SPECIESは_64~_512まであるがハードウェアで対応していないbit数を使おうとするとソフ トウェア処理(=遅くなる)になるので注意 46
  46. JEP 457: Class-File API (Preview) ASM と呼ばれるバイトコード操作・解析フレームワークが(JDK内部にも)ある クラスファイルフォーマットに強く依存するので互換性対応が大変 内部のASMも最新JDKに対応するまで遅延が生じるので、JDK Nで提供されるクラ

    スファイルの新機能を利用するにはJDK N+1以降となるケースがある クラスファイルAPIを提供することでJDK側が互換性を担保 ASM以外にもBCEL(Byte Code Engineering Library)やJavassistのようなクラスフ ァイル形式に強く依存しているフレームワークやツールにも有用 フィールド、メソッド、属性、バイトコード命令などのクラスファイルの実態を不 変のオブジェクトとして扱うAPI クラスファイルの階層的な構造を反映するためにツリー構造を採用 Class - Method - CodeAttribute - ... 1: https://asm.ow2.io/index.html 47
  47. Java void fooBar(boolean z, int x) { if (z) foo(x);

    else bar(x); } ※ ここから「実例」まで、説明のためコードが一部簡略化されています 48
  48. ASM ClassWriter classWriter = ...; MethodVisitor mv = classWriter.visitMethod(0, "fooBar",

    "(ZI)V", null, null); mv.visitCode(); mv.visitVarInsn(ILOAD, 1); Label label1 = new Label(); mv.visitJumpInsn(IFEQ, label1); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "Foo", "foo", "(I)V", false); Label label2 = new Label(); mv.visitJumpInsn(GOTO, label2); mv.visitLabel(label1); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "Foo", "bar", "(I)V", false); mv.visitLabel(label2); mv.visitInsn(RETURN); mv.visitEnd(); 49
  49. Class-File API (java.lang.classfile ) ClassBuilder classBuilder = ...; classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void,

    CD_boolean, CD_int), flags, methodBuilder -> methodBuilder.withCode(codeBuilder -> { Label label1 = codeBuilder.newLabel(); Label label2 = codeBuilder.newLabel(); codeBuilder.iload(1) .ifeq(label1) .aload(0) .iload(2) .invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int)) .goto_(label2) .labelBinding(label1) .aload(0) .iload(2) .invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int)) .labelBinding(label2); .return_(); }); 1: https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/classfile/package-summary.html 50
  50. Class-File API with lambda CodeBuilder classBuilder = ...; classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void,

    CD_boolean, CD_int), flags, methodBuilder -> methodBuilder.withCode(codeBuilder -> { codeBuilder.iload(1) .ifThenElse( // <- b1 -> b1.aload(0) .iload(2) .invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int)), b2 -> b2.aload(0) .iload(2) .invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int))) .return_(); })); 51
  51. Magic number CodeBuilder classBuilder = ...; classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void, CD_boolean, CD_int),

    flags, methodBuilder -> methodBuilder.withCode(codeBuilder -> { int thisArg = codeBuilder.receiverSlot(); int boolArg = codeBuilder.parameterSlot(0); // boolean z int intArg = codeBuilder.parameterSlot(1): // int x codeBuilder.iload(boolArg) // Magic number ではなくなった .ifThenElse( b1 -> b1.aload(thisArg) // 同上 .iload(intArg) // 同上 .invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int)), b2 -> b2.aload(thisArg) .iload(intArg) .invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int))) .return_(); })); 52
  52. import static java.lang.constant.ClassDesc.of; String string = "java.lang.String"; String system =

    "java.lang.System"; String printStream = "java.io.PrintStream"; // "Hello.class" に書き出すため`buildTo(..)` 。`build(..)` で`byte[]` を取得するのがよくある使い方 ClassFile.of().buildTo(Path.of("Hello.class"), of("Hello"), clb -> clb.withFlags(ACC_PUBLIC) .withMethod(INIT_NAME, MTD_void, ACC_PUBLIC, // INIT = instance initialization mb -> mb.withCode( cb -> cb.aload(cb.receiverSlot()) .invokespecial(CD_Object, INIT_NAME, MTD_void) .return_())) .withMethod("main", MethodTypeDesc.of(CD_void, of(string).arrayType()), ACC_STATIC + ACC_PUBLIC, mb -> mb.withCode( cb -> cb.getstatic(of(system), "out", of(printStream)) .ldc("Hello World") .invokevirtual(of(printStream), "println", MethodTypeDesc.of(CD_void, of(string))) .return_()))); 54
  53. Transform 読み込んだクラス情報を書き換えることもできる。 debug から始まるメソッドを削除する例 ClassModel classModel = ClassFile.of().parse(bytes); byte[] newBytes

    = ClassFile.of().build(classModel.thisClass().asSymbol(), classBuilder -> { for (ClassElement ce : classModel) { if (!(ce instanceof MethodModel mm && mm.methodName().stringValue().startsWith("debug"))) { classBuilder.with(ce); } } }); 55
  54. JEP 458: Launch Multi-File Source-Code Programs ソースコードから直接実行する Source-file mode が複数ファイルに対応した。JDK

    11で入っ たJEP 330: Launch Single-File Source-Code Programsの発展型 # Before $ javac <options> -d <memory> --class-path <path> --source-path <root> <.java file> $ java <options> --class-path <memory>:<path> <launch class of .java file> # After $ java <options> --class-path <path> <.java file> jarファイルももちろん * で複数指定可能 56
  55. 流れ 1. ルートディレクトリを決定 2. モジュールを決定 i. ルートディレクトリにmodule-info.javaが存在する場合はそれに則る, OR ii. 無名モジュール

    3. 指定ファイルと関連クラスをコンパイルし、クラスファイルをメモリ内キャッシュに格納 4. 起動クラスを決定 i. 指定ファイルの最初のトップレベルクラスがメインメソッドを持っていればそのクラス ii. 指定ファイルと同じ名前を持つクラスがメインメソッドを持っていればそのクラス iii. エラー 5. カスタムクラスローダー( MemoryClassLoader )により4のメインメソッドを呼び出す 1: https://github.com/openjdk/jdk/commit/517b1788198fc325961df61161f9b365c7b2524e 2: shebangで始まっている場合は1と2はskipされる ( --source-path が空扱い) 3: 以前の実装とは異なる。 Main から切り離されて com.sun.tools.javac.launcher.MemoryClassLoader と独自クラスになった 57
  56. Quiz 以下の Test.java の実行結果は? public class Hoge { public static

    void main(String[] args) { System.out.println("Hoge"); } } public class Test { public static void main(String[] args) { System.out.println("Test"); } } public static void main(String[] args) { System.out.println("Declared"); } $ javac --enable-preview --source 22 Test.java $ java --enable-preview Test # コンパイルするので `--source` が必要 $ java --enable-preview --source 22 Test.java 58
  57. Answer どっちも3のDeclared $ javac --enable-preview --source 22 Test.java Note: Test.java

    uses preview features of Java SE 22. Note: Recompile with -Xlint:preview for details. $ java --enable-preview Test Declared # 念のためクラスファイルは削除 $ java --enable-preview --source 22 Test.java Declared 60
  58. Quiz public class Hoge { public static void main(String[] args)

    { System.out.println("Hoge"); } } public class Test { public static void main(String[] args) { System.out.println("Test"); } } $ javac --enable-preview --source 22 Test.java $ java --enable-preview Test # コンパイルするので `--source` が必要 $ java --enable-preview --source 22 Test.java 61
  59. Answer 4のそれ以外と1のHoge $ javac --enable-preview --source 22 Test.java Test.java:1: error:

    class Hoge is public, should be declared in a file named Hoge.java public class Hoge { ^ エラー1 個 $ java --enable-preview --source 22 Test.java Hoge 流れ のセクションで説明した通り、source-file modeではファイル名で一致したクラスでは なく、最初のトップレベルクラスが優先して選ばれる。普通にコンパイルするとエラー。 63
  60. JEP 459: String Templates (Second Preview) 変数を含む文字列を書きやすくしよう!が目的。今までは以下のように記述が必要だった // 読みづらい String

    s = x + " plus " + y + " equals " + (x + y); // 細かすぎる s = new StringBuilder() .append(x) .append(" plus ") .append(y) .append(" equals ") .append(x + y) .toString(); // 引数の数や型の不一致を招きやすい s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y); s = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y); // 独特すぎる MessageFormat mf = new MessageFormat("{0} plus {1} equals {2}"); s = mf.format(x, y, x + y); 64
  61. 今後はimport不要な組み込みの STR を使って書きやすくなります var x = 10.0; var y =

    20.5; // 変数は \{ で始まり } で閉じる String s = STR."\{ x } plus \{ y } equals \{ x + y }"; s ==> "10.0 plus 20.5 equals 30.5" 文字列変換前のデータ構造がそのまま返る RAW も用意されている StringTemplate.RAW."\{ x } plus \{ y } equals \{ x + y }"; $1 ==> StringTemplate{ fragments = [ "", " plus ", " equals ", "" ], values = [10.0, 20.5, 30.5] } // * 注 *注: 見易さのため改行を入れています 65
  62. Text Block Text Blockも同じように使うことができる s = STR.""" \{ x }

    plus \{ y } equals \{ x + y} """; s ==> "10.0\n plus\n20.5\n equals\n30.5\n" 66
  63. Formatter Formatterも StringTemplate.Processor の1実装である java.util.FormatProcessor を 利用して同じような感じで表現が可能 FormatProcessor.FMT."%2.1f\{ x }

    plus %2.2f\{ y } equals %.3f\{ x + y }"; $1 ==> "10.0 plus 20.50 equals 30.500" // 16 進 var x = 10; var y = 20; FormatProcessor.FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; $2 ==> "0x000a + 0x0014 = 0x001e" JDK 22ではTemplate式の型( StringTemplate.Processor )が明文化された (JLS 15.8.6) が、仕様上の変更であり実際の使い勝手は変わらない。 67
  64. Advanced usage StringTemplate.Processor<JsonObject, RuntimeException> JSON = StringTemplate.Processor.of( // 単純にJsonObject を作成して返しているがValidation

    処理を加えたりもできる (StringTemplate st) -> new JsonObject(st.interpolate()) ); String name = "Joan Smith"; String phone = "555-123-4567"; String address = "1 Maple Drive, Anytown"; // Template 形式でString を書きつつJsonObject を作成する JsonObject doc = JSON.""" { "name": "\{name}", "phone": "\{phone}", "address": "\{address}" }; """; Robustなパーサーが@shinyafoxさんより実装された: https://github.com/bitterfox/json- string-template 68
  65. JEP 462: Structured Concurrency (Second Preview) 並列処理を楽にしたい 「タスクをあるコードブロックでサブタスクに分けて同時進行できるようにし、最 終的にそれらは同じコードブロックに戻る」仕組みで実現 JDK

    21から変更なし JDK 21 で StructuredTaskScope#fork(...) が Future ではなく Subtask を 返すように変更されたので更なるフィードバックを得るためPreviewリトライ JDK 19から続いている (最初はIncubator) 69
  66. // サブタスクで実行させる処理のリスト List<Callable<String>> tasks = ... try (var scope =

    new StructuredTaskScope<String>()) { // scope.fork() によりサブタスクを実行( フォーク) List<Subtask<String>> subtasks = tasks.stream().map(scope::fork).toList(); // サブタスクの完了を待機 scope.join(); // 成功サブタスクと失敗サブタスクを別セットに分割 Map<Boolean, Set<Subtask<String>>> map = subtasks.stream()  .collect(Collectors.partitioningBy( h -> h.state() == Subtask.State.SUCCESS, Collectors.toSet())); } // 全リソースクローズ 70
  67. 実行中のスレッドいずれかが返ってきたら他をすべてシャットダウン ShutdownOnSuccess を利用する try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {

    List<Subtask<String>> subtasks = tasks.stream().map(scope::fork).toList(); // いずれか一つのサブタスクが成功(Subtask.State#SUCCESS) するか、 // すべて失敗(Subtask.State#FAILED) するまで待つ scope.join(); try { // 最初に成功したサブタスクの結果を取得 String result = scope.result(); ... // 成功したタスクの結果を利用した処理 } catch (ExecutionException e) { // すべて失敗した場合はExecutionException が投げられる throw new CustomException(e); } } 71
  68. 実行中のスレッドいずれかが例外を出したらすべてシャットダウン ShutdownOnFailure を利用する try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

    // 結果が欲しいだけなのでSupplier を使う List<Supplier<String>> suppliers = tasks.stream().map(scope::fork).toList(); // いずれか一つのサブタスクが失敗するか、すべて成功するか、10 秒経過するまで待つ // (10 秒過ぎたらTimeoutException) Instant deadline = LocalDateTime.now().plusSeconds(10).toInstant(ZoneOffset.UTC); scope.joinUntil(deadline); // 最初に失敗したサブタスクの例外を補足して伝播、終了 scope.throwIfFailed(e -> new CustomException(e)); // 全てのサブタスクが成功した場合の処理 ( サブタスクの結果(String) を並べてるだけ) String result = suppliers.stream() .map(Supplier::get) .collect(Collectors.joining(", ")); } 72
  69. Before void dukeAdventure() { User user = new User("duke"); process(user);

    } void process(User user){ // このメソッドではUser は使わないが後続処理のために引数に必要 ... // 何らかの処理 subsequentProcess(user); } void subsequentProcess(User user){ ... // User を使った処理 } 75
  70. Before(ThreadLocal) final ThreadLocal<User> USER = new ThreadLocal<>(); void dukeAdventure() {

    USER.set(new User("duke")); process(); } void process(){ // 引数が不要になった ... subsequentProcess(); } void subsequentProcess(){ // User が必要な時に get する User user = USER.get(); ... // User を使った処理 } 76
  71. After final ScopedValue<User> USER = ScopedValue.newInstance(); // 引数は ScopedValue のkey,

    value, Runnable // 渡したRunnable が完了したら即座に解放される ScopedValue.runWhere(USER, new User("duke"), () -> subsequentProcess()); void subsequentProcess(){ User user = USER.get(); // duke ... } 77
  72. Rebinding void subsequentProcess() { User user = USER.get(); // duke

    ... // User に対する処理 // 再バインド (userService から最新のユーザ情報を入手してバインドする例) ScopedValue.runWhere(USER, userService.get(), () -> doAnother() ); // doAnother() が完了すると元の"duke" ユーザにバインドされ直される } void doAnother() { User user = USER.get(); // userService.get() で得たユーザ ... } 78
  73. Inheriting Scoped Values // StructuredTaskScope によって生成された子スレッドは、親スレッドのスコープ値にアクセス可能 // ThreadLocal の場合は、その値が各子スレッドにコピーされてしまっていた(= メモリがどーん)

    ScopedValue.runWhere(USER, new User("duke"), () -> { try (var scope = new StructuredTaskScope<User>()) { scope.fork(() -> childTask()); ... } }); String childTask() { // 使い勝手は同じ User user = USER.get(); // duke ... } 79
  74. JEP 461: Stream Gatherers (Preview) これまで用意されていたもの(例: .map(..) , .filter(..) )しか使えなかったStream

    の中間操作(intermediate operations)のユーザ定義をサポート 終端操作(terminal operations)の java.util.stream.Collector に対する java.util.stream.Gatherer stream.gather(..).gather(..).gather(..).collect(..); andThen(..) がサポートされているので繋げられる built-in gathererは java.util.stream.Gatherers にある 中間操作はこれまでいくつも提案されてきたが、全部追加すると学習コストが急増する カスタム中間操作をどのように形で追加すべきか検討してきたが、終端操作と同じ アプローチでサポートを決定 80
  75. built-in gatherers fold : many-to-one. 入力要素がなくなるまでインクリメントに集約(aggregate). Stream.of(1,2,3,4,5) .gather( Gatherers.fold( ()

    -> "", // initial (current, next) -> { // folder if (!current.isEmpty()) return current + "." + next; return next.toString(); } ) ).toList(); // "," にしたら List と見分けがつかなかったので"." を採用 $1 ==> [1.2.3.4.5] // initial を `() -> "0"` にした場合 $2 ==> [0.1.2.3.4.5] Stream#reduce とは、 (1)順序保証がある (2) BiFunction を引数にするので入力と結果の型 が同じでなくともよい という違いがある。 81
  76. built-in gatherers scan : one-to-one. インクリメンタルな累積(accumulation). Stream.of(1,2,3,4,5) .gather( Gatherers.scan( ()

    -> "", // initial (current, next) -> current + next)) // scanner .toList(); $1 ==> [1, 12, 123, 1234, 12345] 82
  77. Quiz initialの処理を変えました。どうなるでしょうか? Stream.of(1,2,3,4,5) .gather( Gatherers.scan( // 前: () -> "",

    () -> 10, // initial (current, next) -> current + next)) // scanner .toList(); $1 ==> [1, 12, 123, 1234, 12345] # 前の結果 ( 参考用) $2 ==> [10, 1, 12, 123, 1234, 12345] # 最初の要素が10 $3 ==> [11, 22, 133, 1244, 12355] # 10 + 前の結果 $4 ==> [101, 1012, 10123, 101234, 1012345] # "10" + 前の結果 $5 ==> それ以外 83
  78. current (現在の状況)を出力すると分かりやすい Stream.of(1,2,3,4,5).gather(Gatherers.scan( () -> 10, (current, next) -> {

    System.out.println(current); return current + next; })).toList(); 10 // 10 (initial) 11 // 10 + 1 13 // 11 + 2 16 // 13 + 3 20 // 16 + 4 $1 ==> [11, 13, 16, 20, 25] // () -> "" の場合 // "" (initial) 1 // "" + 1 12 // "1" + 2 ( なぜなら "" + 1 = "1" だから) 123 // "12" + 3 1234 // "123" + 4 $2 ==> [1, 12, 123, 1234, 12345] 85
  79. built-in gatherers mapConcurrent : one-to-one gatherer. Virtual Threadを使って第一引数 ( maxConcurrency

    )まで並行に mapper を実行。順序は保持される windowFixed : many-to-many. 順番に詰める windowSliding many-to-many. 一つずつずらしながら末尾まで詰める Stream.of(1,2,3,4,5).gather(Gatherers.windowFixed(3)).toList(); $1 ==> [[1, 2, 3], [4, 5]] Stream.of(1,2,3,4,5).gather(Gatherers.windowSliding(3)).toList(); $2 ==> [[1, 2, 3], [2, 3, 4], [3, 4, 5]] 86
  80. Before windowFixed をterminal operations( Collector )で実装 Stream.iterate(1, i->++i).limit(5) // Stream.of(1,2,3,4,5)

    .collect(Collector.of( () -> new ArrayList<ArrayList<Integer>>(), (groups, element) -> { if (groups.isEmpty() || groups.getLast().size() == 3) { var current = new ArrayList<Integer>(); current.add(element); groups.addLast(current); } else { groups.getLast().add(element); } }, (left, right) -> { throw new UnsupportedOperationException("Cannot be parallelized"); } )); $3 ==> [[1, 2, 3], [4, 5]] 87
  81. もちろん builtin だけではなく Gatherer で自前実装もできる Stream.iterate(1, i->++i).limit(5).gather(Gatherer.ofSequential( // Initializer: creation

    of a new, potentially mutable, state () -> new ArrayList<Integer>(), // Integrator: 引数はstate, element, result Gatherer.Integrator.of((window, element, downstream) -> { window.add((Integer)element); // state に今のelement を入れる if(window.size() == 3) { // 3 つ溜まったらresult に入れる final var oldWindow = window.clone(); // shallow copy window.clear(); // state を初期化 return downstream.push(oldWindow); } return true; // integration が続くかを示す。true は継続、false は中断 }), // Combiner: BinaryOperator を実装する。今回はSequential なので実装しない( できない) // Sequential でないケースでは並列性(parallel().reduce() 相当) のため // `Gatherer.of(..)` を利用して実装するのが良い // Finisher: 最後の処理。ここでは残りを result に入れている (window, downstream) -> { downstream.push(window); } )).toList(); 88
  82. 正実装 java.base/share/classes/java/util/stream/Gatherers.java public static <TR> Gatherer<TR, ?, List<TR>> windowFixed(int windowSize)

    { if (windowSize < 1) throw new IllegalArgumentException("'windowSize' must be greater than zero"); class FixedWindow { Object[] window; int at; FixedWindow() { at = 0; window = new Object[windowSize]; } boolean integrate(TR element, Downstream<? super List<TR>> downstream) { window[at++] = element; if (at < windowSize) { return true; } else { final var oldWindow = window; window = new Object[windowSize]; at = 0; return downstream.push( SharedSecrets.getJavaUtilCollectionAccess() .listFromTrustedArrayNullsAllowed(oldWindow) ); } } 89
  83. void finish(Downstream<? super List<TR>> downstream) { if (at > 0

    && !downstream.isRejecting()) { var lastWindow = new Object[at]; System.arraycopy(window, 0, lastWindow, 0, at); window = null; at = 0; downstream.push( SharedSecrets.getJavaUtilCollectionAccess() .listFromTrustedArrayNullsAllowed(lastWindow) ); } } } return Gatherer.<TR, FixedWindow, List<TR>>ofSequential( // Initializer FixedWindow::new, // Integrator Integrator.<FixedWindow, TR, List<TR>>ofGreedy(FixedWindow::integrate), // Finisher FixedWindow::finish ); } 90
  84. JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview)

    JDK 23. パターンコンテキストでプリミティブ型を使えるようにして instanceof と switch を拡張。Value ObjectとPrimitiveを統合するより先にパターンを対応。 switch (x.getStatus()) { case 0 -> "okay"; case 1 -> "warning"; case 2 -> "error"; case int i -> "unknown status: " + i; } // 三項演算子代わり handle(someParameter, switch (x.getStatus().isOkay()) { case true -> x.getStatus(); case false -> { log("Status is not okay"); yield -1; } }); 91
  85. Head toward Java 22 (and Java 23) KUBOTA Yuji LINEヤフー株式会社

    Japan Java User Group Night Seminar 2024/03/27 92