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. 2023/05/10 1
    Java 20とその先
    オブジェクト指向からデータ指向プログラムへ
    2023/5/10 TechFeed Experts Night#18
    LINE Fukuoka きしだ なおき

    View Slide

  2. 2023/05/10 2
    今日の話

    Java 20/21
    ● オブジェクト指向からデータ中心プログラミングへ

    View Slide

  3. Java 20

    7つのJEP
    ● しかしすべて試用機能
    ● 新たなJEPはひとつだけ

    View Slide

  4. 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

    View Slide

  5. 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 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());
    }

    View Slide

  6. JEP 436: Virtual Thred

    JVM管理のスレッド
    ● 通信待ちに別処理を行うためにしてはOS管理のスレッドは重い
    Thread.ofVirtual().start(() -> System.out.println("hello"))

    View Slide

  7. JEP 437: Structured Concurrency
    ● スレッド間の同期にjoinやwaitを使うと実質的にはgo toに
    ● 管理が大変
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future user = scope.fork(() -> findUser());
    Future 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());
    }

    View Slide

  8. 小さい変更

    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

    View Slide

  9. 大きいプログラムから小さいプログラムへ

    Javaは大きなプログラムを確実に動かすことが主目的だった
    ● オブジェクト指向
    ● 冗長な文法
    ● 勉強や動作確認目的のコードが書きづらい
    ● コンパイルが必要

    mainのためだけにたくさんの入力が必要
    ● 結果表示の文字列生成が面倒
    ● みんなPythonを使ってしまう

    View Slide

  10. 単一ソースコードファイルの実行
    ● 単一ソースコードで完結するコードはJava 11から直接javaコマン
    ドで実行可
    ● 複数ソースコードに拡張予定(JEPドラフト)

    View Slide

  11. mainを最低限の記述で(JEP 445, Java 21)
    ● クラスの定義や、謎のpublic staticが不要に
    ● 学習時にpublic staticやString[]などを見てないことにする必要がある
    ● 学習が進まないと説明不可
    ● 匿名メインクラス
    ● クラス定義不要
    ● インスタンスメソッドとしてmainを定義可能

    static不要
    ● パッケージプライベートでOK

    public不要
    ● 引数不要

    String[] 不要

    System.outも不要になる予定
    void main() {
    System.out.println("Hello world!");
    }

    View Slide

  12. 文字列への式の埋め込み(JEP 430, Java 21)
    ● 文字列に値を埋め込むのが面倒

    +での連結

    printfやString.format / formatted

    String Template

    STR."今日の温度は¥{temp}℃、¥{temp*9/5+32}℉です"

    SQLエスケープなどにも対応可

    SQL.“””
    select *
    from Product
    where name=”{name}”
    ”””

    View Slide

  13. オブジェクト指向からデータ指向プログラミングへ
    ● オブジェクト指向はプログラムのモジュール化の技法
    ● すでにマイクロサービスなどでプログラムは小さくなっている
    ● プログラムをモジュールに分割する必要がない
    ● データ指向プログラミング
    ● データをデータとして扱う
    ● データを扱う場合にはデータがどのような属性をもつかという
    型の定義が大切
    ● 抽象データ型と代数的データ型

    View Slide

  14. クラスの分類
    ● クラスの役割を分類する
    ● クラスには型とモジュールの性質がある
    分類 役割 性質 備考
    システム境界 入出力など外部とのやりとり モジュール+型 オブジェクト指向がむい
    ている。しかしフレーム
    ワークなどがすでに提供
    データ データを保持 型
    処理 手続きを記述 モジュール 型として扱うことはない

    View Slide

  15. 抽象データ型と代数的データ型
    ● 抽象データ型
    ● 型の定義の指針
    ● 代数的データ型
    ● 型の組み合わせの指針

    View Slide

  16. 抽象データ型
    ● データの操作だけ公開することで変更に強く柔軟な型を定義
    ● データ特化の「カプセル化」
    ● 操作としてデータ操作だけを含む
    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

    View Slide

  17. レコードクラス
    ● データを定義するための型

    record Person(String name, int age) {}
    ● 他の型を継承することも、他の型で継承することもできない
    ● インタフェースの実装は可能

    View Slide

  18. データ型のルール
    ● 純粋関数をデータ型に拡張する
    ● 純粋データ型?
    ● 参照透過
    ● 戻り値が引数だけで決まる
    ● 副作用なし
    ● 状態の変更や外部への入出力なし
    ● 参照透過
    ● 戻り値が引数とフィールドだけで決まる
    ● 副作用なし
    ● フィールド変更以外の状態の変更や
    外部への入出力なし

    View Slide

  19. 代数的データ型
    ● 直積型
    ● クラス
    ● 配列
    ● レコードクラス
    ● 直和型
    ● 継承

    sealed

    View Slide

  20. 直積型
    ● 値を組み合わせる型
    ● レコードクラスは直積型を実現するために導入されたと考えれる
    ● 値のとりうるパターンがそれぞれの値のパターン数の積になる

    record A(boolean a1, byte a2) {}

    2 x 256で512とおり

    View Slide

  21. 直和型
    ● いずれかの型になる

    catch (IOException | InterruptedException ex)

    IOExceptionかInterruptedExcdeption

    java.nio.Buffer

    IntBufferかByteBufferか・・・

    View Slide

  22. 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 {}

    View Slide

  23. データの処理を考える
    ● こんな値を合計する
    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));

    View Slide

  24. 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();

    View Slide

  25. パターンマッチ 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();
    }

    View Slide

  26. レーコードパターン
    ● パターンマッチでレコードの分解

    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;
    }

    View Slide

  27. 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不要

    View Slide

  28. 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で動く

    View Slide

  29. 継承で実装してみる
    ● 継承での実装
    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();

    View Slide

  30. 継承のほうがいい?
    ● すっきりして見える
    int total = cart.stream()
    .mapToInt(item ->
    item.product().type().calc(item.amount()))
    .sum();

    View Slide

  31. 継承で実現したときの欠点
    ● 追うときにいろいろなクラスを見る必要がある
    ● 他のデータが関係あるときに対応できない

    1パック170円、3パック500円とか
    ● 場合わけの型が2つあるときれいにかけない
    ● 商品種別と時間帯とか
    ● どちらかの型に場合わけが必要
    ● 業務処理では一箇所にすべての型の処理をまとめるほうがいい
    ● システム境界では他の型が関係することがすくないので継承が有効

    OracleConnectionがMySQLConnectionを気にしたりとかはない

    View Slide

  32. 書籍

    WEB+DB PRESS vol.134
    ● データ指向プログラミング

    View Slide

  33. Java 21に期待

    Java 21が次のLTS

    Record PatternやPattern match for switch、Virtual Threadが標準

    String Templateがpreview
    ● データ指向でいきましょう

    View Slide