|
| 1 | +--- |
| 2 | +title: "コーディング規約を Java 21 対応にアップデートしました" |
| 3 | +date: 2025/09/12 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - コーディング規約 |
| 7 | + - Java |
| 8 | +category: |
| 9 | + - Programming |
| 10 | +thumbnail: /images/2025/20250912a/thumbnail.jpg |
| 11 | +author: 金澤祐輔 |
| 12 | +lede: "フューチャーの GitHub リポジトリで公開している Java コーディング規約を Java 21 対応に更新しましたのでその内容をご紹介します。" |
| 13 | +--- |
| 14 | +<a href="https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html"> |
| 15 | +<img src="/images/2025/20250912a/image.jpg" alt="" width="640" height="427" loading="lazy"> |
| 16 | +</a> |
| 17 | + |
| 18 | +前回の更新から時間が空いてしまいましたが、フューチャーの GitHub リポジトリで公開している Java コーディング規約を Java 21 対応に更新しましたのでその内容をご紹介します。 |
| 19 | + |
| 20 | +- **[Future Enterprise Coding Standards | Javaコーディング規約](https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html)** |
| 21 | + |
| 22 | +過去の紹介記事はこちらです。 |
| 23 | + |
| 24 | +- [Java17対応版!Javaコーディング規約の紹介](/articles/20211007a/) |
| 25 | +- [システム屋さんがうれしいJava8対応のコーディング規約を公開します!!](/articles/20160902/) |
| 26 | + |
| 27 | +## Java 18 から Java 21 の新機能 |
| 28 | + |
| 29 | +前回の LTS 版、 Java 17 からコードの記述に関する機能追加として以下のものがありました。 |
| 30 | + |
| 31 | +- switch でのパターン・マッチング([JEP 441](https://openjdk.org/jeps/441)) |
| 32 | +- レコード・パターン([JEP 440](https://openjdk.org/jeps/440)) |
| 33 | +- 順序を保持するコレクション([JEP 431](https://openjdk.org/jeps/431)) |
| 34 | +- JavaDoc の コードスニペット([JEP 413](https://openjdk.org/jeps/413)) |
| 35 | + |
| 36 | +また、導入タイミングは Java 17 ですが規約に項目を新たに追加しました。 |
| 37 | + |
| 38 | +- シールクラス([JEP 409](https://openjdk.org/jeps/409)) |
| 39 | + |
| 40 | +[前回記事](https://future-architect.github.io/articles/20211007a/)でもご紹介していますが新機能のまとめについてはこちらも大変参考になります。 |
| 41 | + |
| 42 | +- [Oracle Help Center | JDKにおける重要な変更](https://docs.oracle.com/javase/jp/21/migrate/significant-changes-jdk-release.html) |
| 43 | +- [ひしだま’s 技術メモページ | Java新機能(Javaの変更点)](https://www.ne.jp/asahi/hishidama/home/tech/java/uptodate.html#JDK21) |
| 44 | + |
| 45 | +## コーディング規約 |
| 46 | + |
| 47 | +フューチャーでは[Future Enterprise Coding Standards](https://future-architect.github.io/coding-standards/)としてコーディング規約を公開しています。この記事で紹介している Java コーディング規約のほか、SQL コーディング規約、AWS コーディング規約、OpenAPI Specification 規約についても公開していますので興味のある方はぜひリンク先からご覧ください。 |
| 48 | + |
| 49 | +それでは、今回 Java コーディング規約でどのような更新を行ったのか、追加された機能別に簡単にご紹介します。 |
| 50 | + |
| 51 | +## switch でのパターン・マッチング(JEP 441) |
| 52 | + |
| 53 | +Java 16 から導入された`instanceof`を使用した[パターン・マッチング](https://docs.oracle.com/javase/jp/17/language/pattern-matching.html)ですが、Java 21 以降では新たに`switch`文/式にも拡張され、`switch`使用時においても複数パターンごとに実行内容を簡潔に記述できるようになりました(Java 17 時点ではプレビューでした)。 |
| 54 | + |
| 55 | +構文の詳細は [Oracle Help Center | Java言語更新 > switch式および文のパターン・マッチング](https://docs.oracle.com/javase/jp/21/language/pattern-matching-switch.html#GUID-E69EEA63-E204-41B4-AA7F-D58B26A3B232)をご参照ください。 |
| 56 | +以下のように使用します。 |
| 57 | + |
| 58 | +```java switch式の例 |
| 59 | + public static double getPerimeter(Shape s) throws IllegalArgumentException { |
| 60 | + return switch (s) { |
| 61 | + case Rectangle r -> 2 * r.length() + 2 * r.width(); |
| 62 | + case Circle c -> 2 * c.radius() * Math.PI; |
| 63 | + default -> throw new IllegalArgumentException("Unrecognized shape"); |
| 64 | + }; |
| 65 | + } |
| 66 | +``` |
| 67 | + |
| 68 | +```java switch文の例 |
| 69 | + public static double getPerimeter(Shape s) throws IllegalArgumentException { |
| 70 | + switch (s) { |
| 71 | + case Rectangle r: return 2 * r.length() + 2 * r.width(); |
| 72 | + case Circle c: return 2 * c.radius() * Math.PI; |
| 73 | + default: throw new IllegalArgumentException("Unrecognized shape"); |
| 74 | + } |
| 75 | + } |
| 76 | +``` |
| 77 | + |
| 78 | +プレビュー機能が正式採用となりましたので、組み合わせて使用するように推奨しています。 |
| 79 | + |
| 80 | +## レコード・パターン(JEP 440) |
| 81 | + |
| 82 | +Java 16 で導入された[レコード](https://docs.oracle.com/javase/jp/17/language/records.html)にパターン・マッチングが拡張され、データの分解と型チェックを同時に行えるようになりました。詳細は[Oracle Help Center | Java言語更新 > レコード・パターン](https://docs.oracle.com/javase/jp/21/language/record-patterns.html)をご参照ください。 |
| 83 | +以下のように使用します。 |
| 84 | + |
| 85 | +```java |
| 86 | +record Point(double x, double y) {} |
| 87 | + |
| 88 | + static void printAngleFromXAxis(Object obj) { |
| 89 | + if (obj instanceof Point(double x, double y)) { |
| 90 | + System.out.println(Math.toDegrees(Math.atan2(y, x))); |
| 91 | + } |
| 92 | + } |
| 93 | +``` |
| 94 | + |
| 95 | +前述の`switch`でのパターン・マッチングと併用することで網羅性チェックや優先順位チェックの恩恵を受けながら、レコードを分解しつつ分岐を記述することが可能になっています。 |
| 96 | + |
| 97 | +冗長なコードの削減、型安全性の向上、IDEによる補完やリファクタリング支援などのメリットがあり、これによりコードの可読性・保守性・安全性が高まるため、コーディング規約ではレコードを使用する場合はレコードパターンを用いて記述することを推奨しています。 |
| 98 | + |
| 99 | +## 順序を保持するコレクション(JEP 431) |
| 100 | + |
| 101 | +順序をもつコレクション/セット/マップ のための新しいインタフェース群が導入され、先頭・末尾・逆順などの操作を行う first/last/reversed 系メソッドが統一的に提供されました。 |
| 102 | + |
| 103 | +セット/マップのメソッドなどの詳細は[Oracle Help Center | コア・ライブラリ > 順序付きコレクション、セットおよびマップの作成](https://docs.oracle.com/javase/jp/21/core/creating-sequenced-collections-sets-and-maps.html#GUID-DCFE1D88-A0F5-47DE-A816-AEDA50B97523)をご参照ください。 |
| 104 | +利用頻度の高い `List` に注目すると、以下のメソッドが利用できます。 |
| 105 | + |
| 106 | +```java |
| 107 | +interface SequencedCollection<E> extends Collection<E> { |
| 108 | + SequencedCollection<E> reversed(); |
| 109 | + // methods promoted from Deque |
| 110 | + void addFirst(E); |
| 111 | + void addLast(E); |
| 112 | + E getFirst(); |
| 113 | + E getLast(); |
| 114 | + E removeFirst(); |
| 115 | + E removeLast(); |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +これまで `List` で先頭に要素を取得するためには`List#get()`に`0`を指定し、末尾の要素を取得する場合には`List#get()`に`リストのサイズ-1`を指定する形、先頭への要素追加では`List#add()`にインデックスの`0`と`追加する要素`、末尾への追加では`List#add()`に`追加する要素`など操作によって書き方が冗長になってしまうことがありました。 |
| 120 | + |
| 121 | +```java Listでの操作例 |
| 122 | +// 先頭の取得 |
| 123 | +var first = list.get(0); |
| 124 | +// 末尾の取得 |
| 125 | +var last = list.get(list.size() - 1); |
| 126 | +// 先頭に追加 |
| 127 | +var addedFirst = list.add(0, "A"); |
| 128 | +// 末尾に追加 |
| 129 | +var addedLast = list.add("B"); |
| 130 | +// 逆順のリストを得る |
| 131 | +var reversedList = new ArrayList<>(list); |
| 132 | +var Collections.reverse(reversedList); |
| 133 | +``` |
| 134 | + |
| 135 | +`SequencedCollection`として統一されたメソッドを利用することで、他の順序を保持するコレクションの`Deque`や`LinkedHashSet`など種類によって書き方を変えることなく、かつほぼ同等の操作を実現できるため基本的に使用を推奨しつつ状況に合わせて利用方針を統一することとしています。 |
| 136 | + |
| 137 | +::: note warn |
| 138 | +[ArrayList](https://github.com/openjdk/jdk/blob/jdk-21-ga/src/java.base/share/classes/java/util/ArrayList.java)の実装を確認すると`getFirst()` `getLast()`で行っている処理自体は同等ですが、要素がない状態で呼び出した場合にスローされる例外が異なっている点に注意が必要です。 |
| 139 | + |
| 140 | +要素のない状態で`getFirst()` `getLast()`を実行すると`NoSuchElementException`がスローされます。 |
| 141 | +一方で`list.get(0)` `list.get(list.size()-1)`を要素のない状態で実行すると`IndexOutOfBoundsException`がスローされます。 |
| 142 | +::: |
| 143 | + |
| 144 | +::: note warn |
| 145 | +`reversed()`で取得されるのはビューのため元のコレクションの順序を並び替えませんが、参照を保持しているため要素に変更を加えるとその変更が元のコレクションに反映される点には注意が必要です。 |
| 146 | +::: |
| 147 | + |
| 148 | +## JavaDoc の コードスニペット(JEP 413) |
| 149 | + |
| 150 | +Java 18 以降から JavaDoc コメントにて使用できる `{@snippet}`タグが導入されました。インラインと外部ファイル引用をサポートし、属性でハイライトやリージョン抽出などを制御できます。 |
| 151 | + |
| 152 | +詳細は[Oracle Help Center | JavaDocガイド > スニペットのプログラマーズ・ガイド](https://docs.oracle.com/javase/jp/21/javadoc/snippets.html#GUID-909A57AD-71F1-43B6-A570-A13815B9DCED)をご参照ください。 |
| 153 | + |
| 154 | +以下のように使用します。 |
| 155 | + |
| 156 | +```java |
| 157 | + /** |
| 158 | + * ユーザー登録処理の例 |
| 159 | + * {@snippet : |
| 160 | + * User user = new User(); |
| 161 | + * user.setName("未来太郎"); |
| 162 | + * user.setEmail("mirai@example.com"); |
| 163 | + * userRepository.save(user); |
| 164 | + * } |
| 165 | + */ |
| 166 | +``` |
| 167 | + |
| 168 | +以前は JavaDoc で API ドキュメントを作成する場合には、短い例や1行の例では`{@code ...}`、長い例の場合は`<pre>{@code ...}</pre>`のような構造体を使用して、ドキュメント・コメントにソース・コードのフラグメントを記載していました。 |
| 169 | + |
| 170 | +`{@snippet}`タグを使用するとスニペットのレンダリング出力には、コードブロックの左上隅に「クリップボードにコピー」ボタンが配置されるようになるため便利です。 |
| 171 | +また、言語の種類などの属性や強調表示なども指定できるようになるため、こちらの利用を推奨しています。 |
| 172 | + |
| 173 | +昨今の Java 開発での導入状況はどうかというと、JDK 標準ライブラリのドキュメントでも段階的に `@snippet` タグの利用が進められており、Java 21 時点では JDK の Javadoc においてコード例にコピー機能やシンタックスハイライトが付加されるケースが増えています(これらは`@snippet`タグによるものです)。 |
| 174 | + |
| 175 | +- [Collections (Java SE 21 & JDK 21)](https://docs.oracle.com/javase/jp/21/docs/api/java.base/java/util/Collections.html#newSequencedSetFromMap(java.util.SequencedMap)) |
| 176 | + |
| 177 | +主要なJava Webアプリケーション向けOSSライブラリ内や拡張機能での利用状況に目を向けると、Spring Framework においては現時点でほとんど利用されていないものの、Jakarta EE の一部コンポーネントでは`{@snippet}`タグが公式ドキュメントに採用されています。特に Jakarta Persistence(JPA)では積極的に活用しており、アノテーション`@Convert`などの Javadoc に複数行のコード例を`{@snippet}`タグで挿入しています。 |
| 178 | + |
| 179 | +- [Convert (Jakarta Persistence API documentation)](https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/convert) |
| 180 | +- [persistence/api/src/main/java/jakarta/persistence/Convert.java at 3.2-3.2.0-RELEASE · jakartaee/persistence](https://github.com/jakartaee/persistence/blob/3.2-3.2.0-RELEASE/api/src/main/java/jakarta/persistence/Convert.java) |
| 181 | + |
| 182 | +Spring や Micronaut のように長年のコードとドキュメント資産を持つフレームワークでは、古いJavadoc構文からの移行に慎重な様子がうかがえます。一方で、新仕様に沿った Jakarta EE API や先進的な OSS では採用が始まっています。ツール面でもサポートが進んでおり、コード解析ツールの Checkstyle は2025年のバージョン 10.22.0 で Javadoc 内の`@snippet`タグ対応を行いました。 |
| 183 | + |
| 184 | +- [Release Notes – checkstyle > 10.22.0](https://checkstyle.sourceforge.io/releasenotes.html#Release_10.22.0) |
| 185 | + |
| 186 | +こういったツール側の追随や Java 23 で追加される Markdown 形式の JavaDoc コメントと共存可能であることも導入を後押ししている状況かと思います。 |
| 187 | +既存のコード資産の`{@code}`タグについては置き換えの検討を進めつつ、これからの新規実装箇所でまず利用するという方針が良いでしょう。 |
| 188 | + |
| 189 | +## シールクラス(JEP 409) |
| 190 | + |
| 191 | +シールクラスは継承可能な型を明示的に制約できるためコードの拡張範囲を強固にコントロールでき、不特定の継承を禁止して安全にするといった点や、継承関係の設計意図が宣言的に可視化されドキュメント代わりにもなるというメリットがありますが、一方で明確な方針無く利用するとコードの保守性や柔軟性が悪くなるというデメリットが目立ってしまいます。 |
| 192 | + |
| 193 | +シールクラスの利用については状況に応じて利用しないか、利用しても良い箇所について方針を決めた上で使用することとしています。 |
| 194 | +コーディング規約では以下のように補足しています。 |
| 195 | + |
| 196 | +::: note info |
| 197 | +**【補足:シールクラス(sealed classes)とは】** |
| 198 | + Javaのsealedクラスは、継承できるサブクラスを明示的に制限する仕組みです。 |
| 199 | + これにより、ドメインモデルの制約強化やパターンマッチングの網羅性チェックが可能となり、意図しない拡張や誤用を防ぐことができます。 |
| 200 | + 典型的な利用例としては、状態や種類が限定されるドメイン(例:イベント種別、計算式のノード型など)の表現や、パターンマッチング(switch文・式)で全ケースを網羅的に扱いたい場合などが挙げられます。 |
| 201 | + メリットは安全性・可読性の向上ですが、柔軟な拡張が難しくなるデメリットもあるため、利用方針を明確に定めてください。 |
| 202 | +::: |
| 203 | + |
| 204 | +## 最後に |
| 205 | + |
| 206 | +Java 21 は過去のバージョンで追加された機能を強化するものが大半で、Java 17 のコーディング規約で追加された項目への追記が主な変更点となりました。 |
| 207 | + |
| 208 | +前回から引き続き、各プロジェクトや自分のチームで使用しないルールは、削除したり入れ替えたりして使うことを想定しているというスタンスは変わりません。 |
| 209 | +言語のアップデートを取り入れ、また作成済みのルールに関しても環境の変化に合わせて改善していきますので、適用するルールを取捨選択しながら活用いただければ幸いです。 |
| 210 | + |
| 211 | +9月にリリースが控えている次期 LTS バージョンの Java 25 に対応した Java コーディング規約についても準備を進めており公開予定ですので、そちらのアップデートにもご期待ください。 |
0 commit comments