>100 Views
May 29, 26
スライド概要
JJUG CCC 2026 Spring のセッション『switch式で始めるJava流パターンマッチング』の公開資料です。
シンプレクスは1997年の創業以来、メガバンクや大手総合証券を筆頭に、日本を代表する金融機関のテクノロジーパートナーとしてビジネスを展開してきました。現在では、金融領域で培った豊富なノウハウを活用し、金融機関以外の領域でもソリューションを展開しています。2019年3月にはAI企業のDeep Percept株式会社、2021年4月には総合コンサルティングファームのXspear Consulting株式会社がグループに加わり、創業時より付加価値の創造に取り組んできたシンプレクスとワンチームとなって、公的機関や金融機関、各業界をリードする企業のデジタルトランスフォーメーション(DX)の推進を支援しています。
switch式で始める Java流パターンマッチング Java 21 / switch expression / sealed + record シンプレクス株式会社 伊藤悠椰 1 © 2026 Simplex Inc.
今日のゴール 01 パターンマッチングの発想 Javaにパターンマッチングを持ち込む Java 21 の switch は、単に分岐の書き方が変わっただけではなく「パターンマッ チング」の道具となった sealed / record と組み合わせると、扱える形を型で閉じられる 実務では、複雑な分岐をコアロジックだけADT化すると効きやすい 2 © 2026 Simplex Inc.
自己紹介と出会い 01 パターンマッチングの発想 シンプレクス株式会社 リードエンジニア(2023年新卒入社) 主に Java + TypeScript でのシステム開発に従事 学生時代に OCaml で関数型プログラミングに出会う 3 © 2026 Simplex Inc.
OCamlとの出会い 01 パターンマッチングの発想 値の「形」で分岐できる let rec fib = function | 0 -> 0 | 1 -> 1 | n when n > 1 -> fib (n - 1) + fib (n - 2) | _ -> invalid_arg "fib: n must be >= 0" ;; 条件式を積み重ねるのではなく、受け入れるケースを並べる 分岐の見た目が「仕様の表」に近くなる 4 © 2026 Simplex Inc.
パターンマッチングとは 01 パターンマッチングの発想 受理する形を宣言的に並べる式 構造、種類、値の内容で分岐する 分岐内では、取り出した値の型が確定する BNF やオートマトンと同じように「許される形」を先に考える 入力を if で探す 形を match で並べる : : 条件を上から順に評価する 受理できるケースを列挙する 5 © 2026 Simplex Inc.
型で形を表す 01 パターンマッチングの発想 まず、入力として扱う値の種類を型として列挙する Expr ::= | Num | Add(Expr, Expr) | Sub(Expr, Expr) | Mul(Expr, Expr) | Div(Expr, Expr) ADT: Num / Add のように「あり得る形」を列挙した型 AST:式やJSONのような木構造を、ADTなどで表したデータ 処理は、その形ごとのケース列挙になる 6 © 2026 Simplex Inc.
分岐をコンパイラに検査させる 01 パターンマッチングの発想 コンパイラが分岐を検査しやすい パターンマッチングは「ケースの列挙」なので、 網羅性:全ケースを書けているか 到達不能:先に広いケースを書いて、後続ケースが死んでいないか 型の整合:取り出した値の型が分岐内で確定する を機械的に検査できる let classify s = match s with | s -> "string: " ^ s | s when String.length s = 0 -> "<blank>" (* unused *) 7 © 2026 Simplex Inc.
Javaで使える材料 SECTION 02 8 © 2026 Simplex Inc.
Javaへ持ち帰る観点 Javaで使える材料 02 関数型言語の match を、そのまま Java に持ち込むわけではない 形を閉じる:存在するケースを sealed で限定する 分岐を表にする: switch 式でケースを並べる 取り出しを型に任せる:pattern switch / record pattern で値を取り出す この3つが揃うと、Javaでも「読める分岐表」に近づく 9 © 2026 Simplex Inc.
Java 8〜21の流れ Javaで使える材料 02 Java は段階的に「関数型ライク」な書き味を取り込んできた version Java 8 Java 14 Java 17 Java 21 使える材料 ラムダ式 / Stream API switch 式 sealed / record pattern switch この話での役割 処理を値として扱いやすくする 分岐から値を返す データの形を閉じる 型で分岐して値を取り出す 10 © 2026 Simplex Inc.
switch式:値を返す分岐表
switch
02
Javaで使える材料
が式になると、分岐結果をそのまま値として扱える
String msg = switch (code) {
case 200 -> "OK";
case 404 -> "Not Found";
default -> "Other";
};
で分岐ごとの結果が明確になる
代入前の一時変数や break の管理が減る
match に近い「ケース列挙」の読み味になる
case ->
11
© 2026 Simplex Inc.
sealed + record:形を閉じる 02 Javaで使える材料 sealed interface Expr permits Lit, Add, Mul {} record Lit(int n) implements Expr {} record Add(Expr l, Expr r) implements Expr {} record Mul(Expr l, Expr r) implements Expr {} :許可された実装だけに限定する record :値を持つ不変データを短く書く 合わせると、JavaでADTに近い形を作れる sealed 12 © 2026 Simplex Inc.
pattern switch:型で分岐
02
Javaで使える材料
String label(Object value) {
return switch (value) {
case Integer n -> "int: " + n;
case String s -> "str: " + s;
case null
-> "<null>";
default
-> "<unknown>";
};
}
とキャストを分離しなくてよい
分岐の中では変数の型が確定している
case null も分岐表の一部として明示できる
instanceof
13
© 2026 Simplex Inc.
when / case null:穴を埋める
02
Javaで使える材料
String normalize(String s) {
return switch (s) {
case null -> "<null>";
case String x when x.isBlank() -> "<blank>";
case String x -> x;
};
}
null を switch の外で特別扱いしなくてよい
ガード条件は when でケースに添えられる
Javaらしい null 対応を残しつつ、分岐表として読める
14
© 2026 Simplex Inc.
小さなADT例:Expr Expr Javaで使える材料 02 は、式の木構造を表すASTでもあり、形を閉じたADTでもある sealed interface Expr permits Lit, Add, Mul {} record Lit(int n) implements Expr {} record Add(Expr l, Expr r) implements Expr {} record Mul(Expr l, Expr r) implements Expr {} ASTとして見る:式を木構造のデータとして扱う ADTとして見る:式としてあり得る形を型で列挙する 15 © 2026 Simplex Inc.
Java版eval:switchでケース列挙
する
02
Javaで使える材料
int eval(Expr expr) {
return switch (expr) {
case Lit(int n) -> n;
case Add(var l, var r) -> eval(l) + eval(r);
case Mul(var l, var r) -> eval(l) * eval(r);
};
}
が sealed なので、あり得る形が限定される
switch はその形を上から列挙する場所になる
新しい形を増やしたとき、処理漏れに気づきやすい
Expr
16
© 2026 Simplex Inc.
Java流matchの最小セット 02 Javaで使える材料 Javaでパターンマッチングっぽく書くための軸はシンプル でデータの持ち方を明示する sealed で存在する形を閉じる switch 式で形ごとの処理を並べる case null / when で実務上の例外も表に入れる 次は、この考え方をJSON探索に適用する record 17 © 2026 Simplex Inc.
実務例:JSON探索 SECTION 03 18 © 2026 Simplex Inc.
問題設定:user.nameを取り出す 実務例:JSON探索 03 以下のようなJSONレスポンスから user.name を安全に取り出したい { "user": { "name": "Alice", "age": 30 }, "status": "active" } JSONパース自体は Jackson 等のライブラリに任せる 問題は、その後のコアロジックでどんな形として扱うか 19 © 2026 Simplex Inc.
Map/Objectのつらさ
Map<String,Object>
実務例:JSON探索
03
のまま進むと、期待する形が if の中に埋もれる
String readUserName(Object json) {
if (!(json instanceof Map<?,?> root)) return "<err>";
Object userObj = root.get("user");
if (!(userObj instanceof Map<?,?> user)) return "<err>";
Object nameObj = user.get("name");
if (!(nameObj instanceof String name)) return "<err>";
return name;
}
cast / null / 欠損 / 型違いが同じ場所に混ざる
「本当に欲しい形」が読み取りづらい
20
© 2026 Simplex Inc.
発想の転換:一度、閉じた型にする 03 実務例:JSON探索 境界で受けたデータを、コア向けの形に正規化する 外側 : JSON文字列 / JsonNode / Map<String,Object> 境界 : ライブラリでパースし、形を揃える 内側 : sealed interface J として扱う I/Oやライブラリ呼び出しは既存の型でよい 複雑な分岐が集まるコアだけ、扱う形を閉じる 形が閉じると switch が分岐表として効いてくる 21 © 2026 Simplex Inc.
JSON値ADTを先に考える 03 実務例:JSON探索 パース後に扱うJSON値を、まず言語非依存のADTとして見る Json = Null | Bool of boolean | Number of number | String of text | Array of Json list | Object of (text * Json) list これは入力文字列の文法ではなく、パース後のデータ型 Array と Object がまた Json を持つので木になる Javaでは、このADTを sealed + record に落とす 22 © 2026 Simplex Inc.
JavaでADTを表す 03 実務例:JSON探索 JSON値としてあり得る形を、Javaの型として閉じる sealed interface J permits JNull, JBool, JNum, JStr, JArr, JObj {} record JNull() implements J {} record JBool(boolean b) implements J {} record JNum(double n) implements J {} record JStr(String s) implements J {} record JArr(List<J> xs) implements J {} record JObj(Map<String, J> fields) implements J {} ここから先は Object ではなく J を相手に switch できる 23 © 2026 Simplex Inc.
JSON探索実装
user.name
03
実務例:JSON探索
を安全に取り出す。欠損や型違いは empty に落とす
Optional<String> readUserName(J json) {
return switch (json) {
case JObj(var root) -> switch (root.get("user")) {
case JObj(var user) -> switch (user.get("name")) {
case JStr(var name) -> Optional.of(name);
case null, default -> Optional.empty();
};
case null, default -> Optional.empty();
};
default -> Optional.empty();
};
}
24
© 2026 Simplex Inc.
何が良くなったか 実務例:JSON探索 03 → JObj → JStr という期待する形が、そのままコードに出る Map#get の欠損は case null として分岐表に入る 型違いは default に落ち、キャスト失敗として散らばらない 処理の本題が「どの形を受け入れるか」に寄る ifを消すことが目的ではなく、分岐の意味を表に出すことが目的 JObj 25 © 2026 Simplex Inc.
実務に入れる境界 03 実務例:JSON探索 全部をADTにする必要はない 境界:HTTP / JSON / DB / 外部ライブラリの型をそのまま使う 変換:境界で受けた形を、コア向けの結果型や状態型に寄せる コア: sealed + record + switch で分岐を表にする 効果が出やすいのは、状態遷移・APIレスポンス処理・計算ルールのような分岐が密集 する場所 26 © 2026 Simplex Inc.
応用例1:状態遷移
03
実務への展開
状態×イベントの分岐爆発は、switchの表にすると読みやすくなる
型定義は sealed State / sealed Event として閉じておく
State transition(State s, Event e) {
return switch (s) {
case Created __ -> switch (e) {
case Pay __
-> new Paid();
case Cancel __ -> new Cancelled();
default
-> throw new IllegalStateException();
};
case Paid __ -> switch (e) {
case Ship __
-> new Shipped();
case Cancel __ -> new Cancelled();
default
-> throw new IllegalStateException();
};
default -> throw new IllegalStateException();
};
}
27
© 2026 Simplex Inc.
応用例2:APIレスポンス処理
03
実務への展開
境界のレスポンスを、コア向けの Ok / Err(Reason) に正規化する
理由の種類を閉じておくと、以後の処理は理由ごとの表になる
String handle(ApiResult r) {
return switch (r) {
case Ok(var body) -> "OK: " + body;
case Err(var reason) -> switch (reason) {
case RateLimited __ -> "RETRY_LATER";
case NotFound __ -> "SHOW_404";
case ServerError(int status) ->
"ALERT(status=%d)".formatted(status);
};
};
}
28
© 2026 Simplex Inc.
Javaの限界と強み 03 実務への展開 Javaの限界 のような標準コレクション分解は苦手 末尾最適化(TCO)は保証されないため、深い再帰は注意 head::tail Javaの強み / record で設計を揃えるほど switch が強くなる 既存の型システム・IDE・静的解析と相性がよい sealed 29 © 2026 Simplex Inc.
まとめ 03 Javaでパターンマッチングを実務に使う流れ 1. 形を閉じる: sealed / record で扱うケースを限定する 2. switchで並べる:形ごとの処理をケース列挙にする 3. 分岐を表にする:複雑な条件を、読める設計に寄せる 境界はライブラリでOK。コアだけADT化するのが、Java流パターンマッチングの現実 的な使いどころ 30 © 2026 Simplex Inc. Final
アンケートのお願い 03 Survey ご参加ありがとうございました。フィードバックをお願いします 全体アンケート セッションアンケート https://x.gd/e6vlY https://x.gd/SETKa 31 © 2026 Simplex Inc.