Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java 20, 21, オブジェクト指向からデータ指向へ / Java20, 21, Object Oriented to Data Oriented

Java 20, 21, オブジェクト指向からデータ指向へ / Java20, 21, Object Oriented to Data Oriented

2023/5/10(水)に開催されたTechFeed Experts Night#18での登壇資料です。
https://techfeed.io/events/techfeed-experts-night-18

Naoki Kishida

May 10, 2023
Tweet

More Decks by Naoki Kishida

Other Decks in Programming

Transcript

  1. Java 20のJEP • 並列実行関連 • 429: Scoped Values(Incubator) new!!! •

    436: Virtual Threads(2nd Preview) • 437: Structured Concurrency(2nd Incubator) • パターンマッチング • 432: Record Patterns(2nd Preview) • 433: Pattern Matching for switch(4th Preview) • 低レイヤー • 434: Foreign Function & Memory API • 438: Vector API
  2. JEP 429: Scoped Values • メソッド間でのスレッドセーフ共有を手軽に • ThreadLocalはその程度の理由で使うには重い String str;

    void start() { str = "test"; proc1(); } void proc1() { System.out.println(str); proc2(); } void proc2() { System.out.println(str); } final ScopedValue<String> STR = new ScopedValue<>(); void start() { ScopedValue.where(STR, "test") .run(() -> proc1()); } void proc1() { System.out.println(STR.get()); proc2(); } void proc2() { System.out.println(STR.get()); }
  3. JEP 437: Structured Concurrency • スレッド間の同期にjoinやwaitを使うと実質的にはgo toに • 管理が大変 try

    (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Future<String> user = scope.fork(() -> findUser()); Future<Integer> order = scope.fork(() -> fetchOrder()); scope.join(); // Join both forks scope.throwIfFailed(); // ... and propagate errors // Here, both forks have succeeded, so compose their results return new Response(user.resultNow(), order.resultNow()); }
  4. 小さい変更 • URLのコンストラクタが非推奨に • スペースが含まれる場合などURLのチェックが行われていなかった • URIを使う • URI.create(“http://example.com”).toURL() •

    16bit浮動小数点数への対応 • 計算機能はなく変換のみ • データ交換用 jshell> Float.floatToFloat16((float)3.14) $11 ==> 16968 jshell> Float.float16ToFloat((short)16968) $12 ==> 3.140625
  5. mainを最低限の記述で(JEP 445, Java 21) • クラスの定義や、謎のpublic staticが不要に • 学習時にpublic staticやString[]などを見てないことにする必要がある

    • 学習が進まないと説明不可 • 匿名メインクラス • クラス定義不要 • インスタンスメソッドとしてmainを定義可能 • static不要 • パッケージプライベートでOK • public不要 • 引数不要 • String[] 不要 • System.outも不要になる予定 void main() { System.out.println("Hello world!"); }
  6. 文字列への式の埋め込み(JEP 430, Java 21) • 文字列に値を埋め込むのが面倒 • +での連結 • printfやString.format

    / formatted • String Template • STR."今日の温度は¥{temp}℃、¥{temp*9/5+32}℉です" • SQLエスケープなどにも対応可 • SQL.“”” select * from Product where name=”{name}” ”””
  7. クラスの分類 • クラスの役割を分類する • クラスには型とモジュールの性質がある 分類 役割 性質 備考 システム境界

    入出力など外部とのやりとり モジュール+型 オブジェクト指向がむい ている。しかしフレーム ワークなどがすでに提供 データ データを保持 型 処理 手続きを記述 モジュール 型として扱うことはない
  8. 抽象データ型 • データの操作だけ公開することで変更に強く柔軟な型を定義 • データ特化の「カプセル化」 • 操作としてデータ操作だけを含む class PersonName {

    PersonName (String surname, String givenName); String getFullName(); String getSurname(); String getGivenName(); } class PersonName { private String surname; private String givenName; PersonName (String surname, String givenName) { this.surname = surname; this.givenName = givenName; } String getFullName() { return "%s %s".formatted(surname, givenName); } ... class PersonName { private String fullName; PersonName (String surname, String givenName) { this.fullName = surname + " " + givenName; } String getFullName() { return fullName; } String getSurname() { return fullName.split(" ")[0]; } ... どちらでも OK
  9. レコードクラス • データを定義するための型 • record Person(String name, int age) {}

    • 他の型を継承することも、他の型で継承することもできない • インタフェースの実装は可能
  10. データ型のルール • 純粋関数をデータ型に拡張する • 純粋データ型? • 参照透過 • 戻り値が引数だけで決まる •

    副作用なし • 状態の変更や外部への入出力なし • 参照透過 • 戻り値が引数とフィールドだけで決まる • 副作用なし • フィールド変更以外の状態の変更や 外部への入出力なし
  11. 直和型 • いずれかの型になる • catch (IOException | InterruptedException ex) •

    IOExceptionかInterruptedExcdeption • java.nio.Buffer • IntBufferかByteBufferか・・・
  12. Sealed型 • java.nio.Bufferのように継承を使う場合 • コンストラクタをパッケージプライベートにすると限定できる • javaパッケージであれば新たに追加されることはない • アプリケーションクラスではJARをあとから追加が可能 •

    可能な型を限定できない • sealed型で限定 sealed interface Type { record Bulk(int price, int unit) implements Type {} record Packed(int price) implements Type {} } sealed interface Type permits Bulk, Packed {} record Bulk(int price, int unit) implements Type {} record Packed(int price) implements Type {}
  13. データの処理を考える • こんな値を合計する record Product(String name, Type type) {} record

    Item(Product product, int amount) {} var cart = List.of( new Item(new Product("餃子", new Packed(300)), 3), new Item(new Product("牛肉", new Bulk(250, 100)), 230));
  14. Streamで書いてみる • 次のようになる int total = cart.stream() .mapToInt(item -> {

    int amount = item.amount(); Type type = item.product().type(); if (type instanceof Bulk) { Bulk b = (Bulk)type; return b.price() * item.amount() / b.unit(); } else if(type instanceof Packed) { Packed p = (Packed)type; return p.price() * item.amount(); } else { throw new IllegalArgumentException(); } }).sum();
  15. パターンマッチ for instanceof • instanceofとキャストがダサい • パターンマッチの導入 if (type instanceof

    Bulk) { Bulk b = (Bulk)type; return b.price() * item.amount() / b.unit(); } if (type instanceof Bulk b) { return b.price() * item.amount() / b.unit(); }
  16. レーコードパターン • パターンマッチでレコードの分解 • JEP 432: Record Patterns(2nd Preview) Java

    21 ※ でstandard if (type instanceof Bulk b) { return b.price() * item.amount() / b.unit(); } if (type instanceof Bulk(int price, int unit) { return price * item.amount() / unit; }
  17. Switchでのパターンマッチング • JEP 433: Pattern Matching for Switch(4th Preview), Java

    21でstd if (type instanceof Bulk) { Bulk b = (Bulk)type; return b.price() * item.amount() / b.unit(); } else if(type instanceof Packed) { Packed p = (Packed)type; return p.price() * item.amount(); } else { throw new IllegalArgumentException(); } switch (type) case Bulk(int price, int unit) -> { return b.price() * item.amount() / b.unit(); } case Packed p -> { return p.price() * item.amount(); } } ※ sealedで型が限定されているのでdefault不要
  18. Switch式 • Java 14で正式導入 int total = cart.stream() .mapToInt(item ->

    switch(item) { case Item(Product(var n, Packed(int price)), int amount) -> price * amount; case Item(Product(var n, Bulk(int price, int unit)), int amount) -> price * amount / unit; }).sum(); ※ バグを踏んだので、このコードはJava 20で動きません ※ 4月リリースのJava 20.0.1で動く
  19. 継承で実装してみる • 継承での実装 sealed interface Type { /** 量り売り */

    record Bulk(int price, int unit) implements Type { int calc(int amount) { return price * amount / unit; } } /** パッケージ */ record Packed(int price) implements Type { int calc(int amount) { return price * amount; } } int calc(int amount); } int total = cart.stream() .mapToInt(item -> item.product().type().calc(item.amount())) .sum();
  20. 継承で実現したときの欠点 • 追うときにいろいろなクラスを見る必要がある • 他のデータが関係あるときに対応できない • 1パック170円、3パック500円とか • 場合わけの型が2つあるときれいにかけない •

    商品種別と時間帯とか • どちらかの型に場合わけが必要 • 業務処理では一箇所にすべての型の処理をまとめるほうがいい • システム境界では他の型が関係することがすくないので継承が有効 • OracleConnectionがMySQLConnectionを気にしたりとかはない
  21. Java 21に期待 • Java 21が次のLTS • Record PatternやPattern match for

    switch、Virtual Threadが標準 • String Templateがpreview • データ指向でいきましょう