Slide 1

Slide 1 text

Java 8 から 21 へのバージョンアップでどう変わる? 実アプリケーションで学ぶ実装のポイント 2023-11-11 JJUG CCC Fall 2023 BABY JOB 株式会社 浅野 正貴(@mackey0225)

Slide 2

Slide 2 text

Java 8 から 21 へのバージョンアップでどう変わる? 目次 ❏ 自己紹介 ❏ 前提 ❏ 取り上げる機能について ❏ 各機能について実装(Before / After) ❏ まとめ 2

Slide 3

Slide 3 text

Java 8 から 21 へのバージョンアップでどう変わる? 自己紹介 名前:浅野 正貴 所属:BABY JOB 株式会社(2022-06 入社) 役割:Javaエンジニア X: @mackey0225 / GitHub: @mackey0225 3

Slide 4

Slide 4 text

本題の前に

Slide 5

Slide 5 text

Java 8 から 21 へのバージョンアップでどう変わる? 5 前提 ● プロダクト ○ Java 8 (Amazon Corretto) + Spring Boot 2.7 ■ Corretto は当初 Elastic Beanstalk を使っていた名残 ● 背景 ○ Java 8 のサポート期間は無問題󰢏 ○ Spring Boot 2.7 の LTS の期限が間近 ■ Spring Boot 3系だと、Java 8 では対象外󰢃 ■ こっちがモチベーション🔥

Slide 6

Slide 6 text

Java 8 から 21 へのバージョンアップでどう変わる? 6 今日話すこと ● Java 8 → 21 で実装がどの様に変わるのか ○ 焦点は 実装 に関する部分 ■ 可読性や保守性、DDDの観点で良さそうなもの ■ JEP でいうと specification / language を中心 ■ Garbage Collection や Virtual Thread などは対象外 ● 目新しいことはあまりないかも。。。 ○ 「何番煎じだろうが私はまだ煎じていない!」 ○ とはいえ、何か持って帰ってもらえるようにしているつもり󰢛

Slide 7

Slide 7 text

Java 8 から 21 へのバージョンアップでどう変わる? 今日話すこと ● 取り上げる機能 ○ Optional ○ Switch 式 ○ Records 本当は String Template とかも取り上げたかったけど、 時間的に限界でした。。。󰢛 7

Slide 8

Slide 8 text

ここから、本題

Slide 9

Slide 9 text

Java 8 から 21 へのバージョンアップでどう変わる? Optional 9 【概要】 ● Optional 自体は Java 8 で追加 ● Java 9, 10, 11 にてメソッドが追加 ○ Java 9 ■ ifPresentOrElse / or / stream ○ Java 10 ■ orElseThrow ○ Java 11 ■ isEmpty

Slide 10

Slide 10 text

Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【Before】 10 var op = repository.findById(id); if (op.isPresent()) { anotherRepository.save(AnotherEntity.createOnPresent(op.get())); } else { anotherRepository.save(AnotherEntity.createOnAbsence()); } 処理イメージとしては、 リポジトリから Optional で返ってきたときに処理が分かれる場合: - エンティティが取得できる - そのエンティティをベースに別エンティティを生成し、永続化 - エンティティが取得できない - 別のメソッドで別エンティティを生成し、永続化

Slide 11

Slide 11 text

Java 8 から 21 へのバージョンアップでどう変わる? Optional - ifPresentOrElse【After】 11 var op = repository.findById(id); op.ifPresentOrElse( e -> anotherRepository.save(AnotherEntity.createOnPresent(e)), () -> anotherRepository.save(AnotherEntity.createOnAbsence()) ); if文がなくなりスッキリとしつつも、処理分岐感が薄れるので、 チームや組織の方針に合わせて使い分け・統一するのが良い

Slide 12

Slide 12 text

Java 8 から 21 へのバージョンアップでどう変わる? Switch 式 12 【概要】 ● Java 12,13 にて Preview ○ JEP 325: Switch Expressions (Preview) ○ JEP 354: Switch Expressions (Second Preview) ● Java 14 にて正式追加 ○ JEP 361: Switch Expressions

Slide 13

Slide 13 text

Java 8 から 21 へのバージョンアップでどう変わる? Switch 式 13 【簡単な機能・良いとこ】 ● 「文」だけではなく「式」として利用可能 ○ つまり、値を返す ● Arrow label でスッキリした見た目にできる ○ 詳細は後述のコードで説明 ● 網羅性チェックもある ○ 保守開発時の考慮漏れも防げる

Slide 14

Slide 14 text

Java 8 から 21 へのバージョンアップでどう変わる? 14 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 Enum(destinationType)の値によって、destination に代入する値を変える。

Slide 15

Slide 15 text

Java 8 から 21 へのバージョンアップでどう変わる? 15 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 switch 内で代入するために、直前で宣言している

Slide 16

Slide 16 text

Java 8 から 21 へのバージョンアップでどう変わる? 16 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 条件ごとに case の行を定義しないといけない

Slide 17

Slide 17 text

Java 8 から 21 へのバージョンアップでどう変わる? 17 Destination destination; switch (destinationType) { case NURSERY: case CORPORATION: destination = new Destination(nurseryAddress); break; case ANOTHER: destination = new Destination(anotherAddress); break; default: throw new UnsupportedOperationException("未サポートの届け先種類 ") } Switch 式【Before】 󰢃 case ごとに break 文の記載

Slide 18

Slide 18 text

Java 8 から 21 へのバージョンアップでどう変わる? 18 Destination destination = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】

Slide 19

Slide 19 text

Java 8 から 21 へのバージョンアップでどう変わる? 19 Destination destination = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 宣言時に値を設定できるので、 場合によっては final 付加してイミュータブルにできる

Slide 20

Slide 20 text

Java 8 から 21 へのバージョンアップでどう変わる? 20 Destination destination = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 case も1行にまとめられる

Slide 21

Slide 21 text

Java 8 から 21 へのバージョンアップでどう変わる? 21 Destination destination = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 󰢏 冗長な break 文が排除できた

Slide 22

Slide 22 text

Java 8 から 21 へのバージョンアップでどう変わる? 22 Destination destination = switch (destinationType) { case NURSERY, CORPORATION -> new Destination(nurseryAddress); case ANOTHER -> new Destination(anotherAddress); default -> throw new UnsupportedOperationException("未サポートの届け先種類 "); }; Switch 式【After】 参考までに、Arrow labels ではなく、yield も登場 cf.https://openjdk.org/jeps/354#Yielding-a-value (適材適所が前提ですが、個人的には、Arrow labels 使いがちです)

Slide 23

Slide 23 text

Java 8 から 21 へのバージョンアップでどう変わる? 23 ・・・おや!? switch のようすが・・・!

Slide 24

Slide 24 text

Java 8 から 21 へのバージョンアップでどう変わる? Pattern Matching for switch 24 【概要】 ● Java 17〜20 にて Preview ○ JEP 406: Pattern Matching for switch (Preview) ○ JEP 420: Pattern Matching for switch (Second Preview) ○ JEP 427: Pattern Matching for switch (Third Preview) ○ JEP 433: Pattern Matching for switch (Fourth Preview) ● Java 21 にて正式追加 ○ JEP 441: Pattern Matching for switch 何回、Bボタン 押されてんねん!

Slide 25

Slide 25 text

Java 8 から 21 へのバージョンアップでどう変わる? 25 Pattern Matching for switch【Before】 if (item.getType() == null) { return item.getName(); } return switch (item.getType()) { case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") };

Slide 26

Slide 26 text

Java 8 から 21 へのバージョンアップでどう変わる? 26 if (item.getType() == null) { return item.getName(); } return switch (item.getType()) { case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; Pattern Matching for switch【Before】 󰢃 NPE を回避するために、直前でチェックしている

Slide 27

Slide 27 text

Java 8 から 21 へのバージョンアップでどう変わる? 27 Pattern Matching for switch【After】 return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") };

Slide 28

Slide 28 text

Java 8 から 21 へのバージョンアップでどう変わる? 28 return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; Pattern Matching for switch【After】 󰢏 NULL もラベルとして使えるので、 NPE を回避するための処理が不要。

Slide 29

Slide 29 text

Java 8 から 21 へのバージョンアップでどう変わる? 29 Pattern Matching for switch【After】 ただし、この例だと、列挙値で NULL が来るかもという設計がそもそも怪しいかも。。。🤨 return switch (item.getType()) { case null -> item.getName() case DIAPER, BABY_WIPES -> .... case APRON -> .... default -> throw new UnsupportedOperationException("未サポートの商品種別 ") }; 使えるから飛びつくのではなく、別の箇所や設計を見直すことは常日頃から重要!

Slide 30

Slide 30 text

Java 8 から 21 へのバージョンアップでどう変わる? Records 30 【概要】 ● Java 14,15 にて Preview ○ JEP 359: Records (Preview) ○ JEP 384: Records (Second Preview) ● Java 16 にて正式追加 ○ JEP 395: Records

Slide 31

Slide 31 text

Java 8 から 21 へのバージョンアップでどう変わる? Records 31 【簡単な機能・良いとこ】 ● 手軽にイミュータブルなクラスが作成できる ○ 記述量が少なく利用できる ○ コンストラクタや equals などの定義が不要 ■ 独自の仕様・実装をオーバーライドも可能 ○ フィールドは常に private final が付与

Slide 32

Slide 32 text

Java 8 から 21 へのバージョンアップでどう変わる? Records - イメージ 32 class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y == y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } } record Point(int x, int y) { } 少ない記述量でイミュータブルなクラスがで きる。 ※参照のアクセサメソッドは"get"始まりで はない

Slide 33

Slide 33 text

Java 8 から 21 へのバージョンアップでどう変わる? Records - Value Object、DTO 33 イミュータブルである性質を使って、DDDの文脈で ● Value Object ● DTO を定義するのに活かせる。 ※図表は、松岡幸一郎(@little_hand_s).ドメイン駆動設計 モデリング/実装ガ イド.p.79 図 7.1 より https://booth.pm/ja/items/1835632

Slide 34

Slide 34 text

Java 8 から 21 へのバージョンアップでどう変わる? 34 Records - 例:分数(from JEP 395) record Rational(int num, int denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く

Slide 35

Slide 35 text

Java 8 から 21 へのバージョンアップでどう変わる? 35 record Rational(int num, int denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く Records - 例:分数(from JEP 395) コンストラクタを上書き。 入力値をそのままではなく、通分して生成する。 これで、分数としてのルール(=ドメイン知識)を保てる。

Slide 36

Slide 36 text

Java 8 から 21 へのバージョンアップでどう変わる? 36 お分かり頂けただろうか。。。

Slide 37

Slide 37 text

Java 8 から 21 へのバージョンアップでどう変わる? 37 record Rational(int num, int denom) { public Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } private int gcd(int i, int j) { 最大公約数を求める } ... 続く Records - 例:分数(from JEP 395) // 普通のコンストラクタ public Rational(int num, int denom) { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; this.num = num; this.denom = denom; } 󰢏 コンストラクタも簡略化できる! ただし、レコードのヘッダーと一致する場合に限る。

Slide 38

Slide 38 text

Java 8 から 21 へのバージョンアップでどう変わる? 38 ... 続き public Rational add(Rational r) { return new Rational( this.num * r.denom + r.num * this.denom, this.denom * r.denom ); } public Rational multiply(Rational r) { return new Rational( this.num * r.num, this.denom * r.denom ); } Records - 例:分数(from JEP 395)

Slide 39

Slide 39 text

Java 8 から 21 へのバージョンアップでどう変わる? 39 ... 続き public Rational add(Rational r) { return new Rational( this.num * r.denom + r.num * this.denom, this.denom * r.denom ); } public Rational multiply(Rational r) { return new Rational( this.num * r.num, this.denom * r.denom ); } 足し算の際に新しいインスタンスを生成する。 分数のルールを呼び出し側に漏れ出さないように定義できる。 ドメインルールの流出を防げる Records - 例:分数(from JEP 395)

Slide 40

Slide 40 text

さいごに

Slide 41

Slide 41 text

Java 8 から 21 へのバージョンアップでどう変わる? まとめ 41 ● 進化を取り入れて、サービス利用者の方々に還元できるかも。。。? ○ 冗長なコードや記述量が減ったり、 ○ 可読性が良くなったり、 ○ 保守性が高くなったり、 ● とはいえ、 ○ サービス開発においては組織での意思決定・取捨選択は必要 ○ コーディングルールの策定やバージョンアップ戦略も必要 ● なので、日頃からの情報のアップデートは必要!

Slide 42

Slide 42 text

ご清聴いただきありがとうございました。

Slide 43

Slide 43 text

Java 8 から 21 へのバージョンアップでどう変わる? 補足 - IntelliJ IDEA の コードインスペクション機能 43 インスペクション内に「Java 言語レベルの移行支援」がある プロジェクト内で適用できそうな箇所をサジェストしてくれる