Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

2023/03/31 2 今日の話 ● Java 20 ● 大きなプログラムから小さなプログラムへ ● オブジェクト指向からデータ中心プログラミングへ

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

小さい変更 ● 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

Slide 9

Slide 9 text

大きいプログラムから小さいプログラムへ ● Javaは大きなプログラムを確実に動かすことが主目的だった ● オブジェクト指向 ● 冗長な文法 ● 勉強や動作確認目的のコードが書きづらい ● コンパイルが必要 ● mainのためだけにたくさんの入力が必要 ● 結果表示の文字列生成が面倒 ● みんなPythonを使ってしまう

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

mainを最低限の記述で(JEPドラフト) ● クラスの定義や、謎のpublic staticが不要に ● 学習時にpublic staticやString[]などを見てないことにする必要がある ● 学習が進まないと説明不可 ● 匿名メインクラス ● クラス定義不要 ● インスタンスメソッドとしてmainを定義可能 ● static不要 ● パッケージプライベートでOK ● public不要 ● 引数不要 ● String[] 不要 ● System.outも不要になる予定 void main() { System.out.println("Hello world!"); }

Slide 12

Slide 12 text

文字列への式の埋め込み(JEP 430) ● 文字列に値を埋め込むのが面倒 ● +での連結 ● printfやString.format / formatted ● String Template ● STR."今日の温度は¥{temp}℃、¥{temp*9/5+32}℉です" ● SQLエスケープなどにも対応可 ● SQL.“”” select * from Product where name=”{name}” ”””

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

直積型 ● 値を組み合わせる型 ● レコードクラスは直積型を実現するために導入されたと考えれる ● 値のとりうるパターンがそれぞれの値のパターン数の積になる ● record A(boolean a1, byte a2) {} ● 2 x 256で512とおり

Slide 21

Slide 21 text

直和型 ● いずれかの型になる ● catch (IOException | InterruptedException ex) ● IOExceptionかInterruptedExcdeption ● java.nio.Buffer ● IntBufferかByteBufferか・・・

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

レーコードパターン ● パターンマッチでレコードの分解 ● 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; }

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

継承で実現したときの欠点 ● 追うときにいろいろなクラスを見る必要がある ● 他のデータが関係あるときに対応できない ● 1パック170円、3パック500円とか ● 場合わけの型が2つあるときれいにかけない ● どちらかの型に場合わけが必要 ● 業務処理では一箇所にすべての型の処理をまとめるほうがいい ● システム境界では他の型が関係することがすくないので継承が有効 ● OracleConnectionがMySQLConnectionを気にしたりとかはない

Slide 32

Slide 32 text

Java 21に期待 ● Java 21が次のLTS ● Record PatternやPattern match for switch、Virtual Threadが標準 ● データ指向でいきましょう