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

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

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

2023/3/31のJJUGセミナーでの資料です

Naoki Kishida

March 31, 2023
Tweet

More Decks by Naoki Kishida

Other Decks in Programming

Transcript

  1. 2023/03/31 1
    Java 20とその先
    大きなプログラムから小さなプログラムへ
    オブジェクト指向からデータ指向プログラムへ
    2023/3/31 JJUGナイトセミナー「Java 20 リリース記念イベント」
    LINE Fukuoka きしだ なおき

    View full-size slide

  2. 2023/03/31 2
    今日の話

    Java 20
    ● 大きなプログラムから小さなプログラムへ
    ● オブジェクト指向からデータ中心プログラミングへ

    View full-size slide

  3. Java 20

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

    View full-size 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 full-size slide

  5. 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 full-size slide

  6. Virtual Thred

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

    View full-size slide

  7. 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 full-size 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 full-size slide

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

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

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

    View full-size slide

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

    View full-size slide

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

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

    public不要
    ● 引数不要

    String[] 不要

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

    View full-size slide

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

    +での連結

    printfやString.format / formatted

    String Template

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

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

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

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

    View full-size slide

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

    View full-size slide

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

    sealed

    View full-size slide

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

    record A(boolean a1, byte a2) {}

    2 x 256で512とおり

    View full-size slide

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

    catch (IOException | InterruptedException ex)

    IOExceptionかInterruptedExcdeption

    java.nio.Buffer

    IntBufferかByteBufferか・・・

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    JEP 432: Record Patterns(2nd Preview)
    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 full-size slide

  27. Switchでのパターンマッチング

    JEP 433: Pattern Matching for Switch(4th Preview)
    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();
    }
    }

    View full-size 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();

    View full-size 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 full-size slide

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

    View full-size slide

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

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

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

    View full-size slide

  32. Java 21に期待

    Java 21が次のLTS

    Record PatternやPattern match for switch、Virtual Threadが標準
    ● データ指向でいきましょう

    View full-size slide