フューチャー技術ブログ

コーディング規約を Java 21 対応にアップデートしました

前回の更新から時間が空いてしまいましたが、フューチャーの GitHub リポジトリで公開している Java コーディング規約を Java 21 対応に更新しましたのでその内容をご紹介します。

過去の紹介記事はこちらです。

Java 18 から Java 21 の新機能

前回の LTS 版、 Java 17 からコードの記述に関する機能追加として以下のものがありました。

  • switch でのパターン・マッチング(JEP 441
  • レコード・パターン(JEP 440
  • 順序を保持するコレクション(JEP 431
  • JavaDoc の コードスニペット(JEP 413

また、導入タイミングは Java 17 ですが規約に項目を新たに追加しました。

前回記事でもご紹介していますが新機能のまとめについてはこちらも大変参考になります。

コーディング規約

フューチャーではFuture Enterprise Coding Standardsとしてコーディング規約を公開しています。この記事で紹介している Java コーディング規約のほか、SQL コーディング規約、AWS コーディング規約、OpenAPI Specification 規約についても公開していますので興味のある方はぜひリンク先からご覧ください。

それでは、今回 Java コーディング規約でどのような更新を行ったのか、追加された機能別に簡単にご紹介します。

switch でのパターン・マッチング(JEP 441)

Java 16 から導入されたinstanceofを使用したパターン・マッチングですが、Java 21 以降では新たにswitch文/式にも拡張され、switch使用時においても複数パターンごとに実行内容を簡潔に記述できるようになりました(Java 17 時点ではプレビューでした)。

構文の詳細は Oracle Help Center | Java言語更新 > switch式および文のパターン・マッチングをご参照ください。
以下のように使用します。

switch式の例
public static double getPerimeter(Shape s) throws IllegalArgumentException {
return switch (s) {
case Rectangle r -> 2 * r.length() + 2 * r.width();
case Circle c -> 2 * c.radius() * Math.PI;
default -> throw new IllegalArgumentException("Unrecognized shape");
};
}
switch文の例
public static double getPerimeter(Shape s) throws IllegalArgumentException {
switch (s) {
case Rectangle r: return 2 * r.length() + 2 * r.width();
case Circle c: return 2 * c.radius() * Math.PI;
default: throw new IllegalArgumentException("Unrecognized shape");
}
}

プレビュー機能が正式採用となりましたので、組み合わせて使用するように推奨しています。

レコード・パターン(JEP 440)

Java 16 で導入されたレコードにパターン・マッチングが拡張され、データの分解と型チェックを同時に行えるようになりました。詳細はOracle Help Center | Java言語更新 > レコード・パターンをご参照ください。
以下のように使用します。

record Point(double x, double y) {}

static void printAngleFromXAxis(Object obj) {
if (obj instanceof Point(double x, double y)) {
System.out.println(Math.toDegrees(Math.atan2(y, x)));
}
}

前述のswitchでのパターン・マッチングと併用することで網羅性チェックや優先順位チェックの恩恵を受けながら、レコードを分解しつつ分岐を記述することが可能になっています。

switchでのパターン・マッチング例
record MyPair<T, U>(T x, U y) { }
static void recordInference(MyPair<String, Integer> p){
switch (p) {
case MyPair(var s, var i) ->
System.out.println(s + ", #" + i);
}
}

冗長なコードの削減、型安全性の向上、IDEによる補完やリファクタリング支援などのメリットがあり、これによりコードの可読性・保守性・安全性が高まるため、コーディング規約ではレコードを使用する場合はレコードパターンを用いて記述することを推奨しています。

順序を保持するコレクション(JEP 431)

順序をもつコレクション/セット/マップ のための新しいインタフェース群が導入され、先頭・末尾・逆順などの操作を行う first/last/reversed 系メソッドが統一的に提供されました。

セット/マップのメソッドなどの詳細はOracle Help Center | コア・ライブラリ > 順序付きコレクション、セットおよびマップの作成をご参照ください。
利用頻度の高い List に注目すると、以下のメソッドが利用できます。

interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}

これまで List で先頭に要素を取得するためにはList#get()0を指定し、末尾の要素を取得する場合にはList#get()リストのサイズ-1を指定する形、先頭への要素追加ではList#add()にインデックスの0追加する要素、末尾への追加ではList#add()追加する要素など操作によって書き方が冗長になってしまうことがありました。

Listでの操作例
// 先頭の取得
var first = list.get(0);
// 末尾の取得
var last = list.get(list.size() - 1);
// 先頭に追加
var addedFirst = list.add(0, "A");
// 末尾に追加
var addedLast = list.add("B");
// 逆順のリストを得る
var reversedList = new ArrayList<>(list);
var Collections.reverse(reversedList);

SequencedCollectionとして統一されたメソッドを利用することで、他の順序を保持するコレクションのDequeLinkedHashSetなど種類によって書き方を変えることなく、かつほぼ同等の操作を実現できるため基本的に使用を推奨しつつ状況に合わせて利用方針を統一することとしています。

ArrayListの実装を確認するとgetFirst() getLast()で行っている処理自体は同等ですが、要素がない状態で呼び出した場合にスローされる例外が異なっている点に注意が必要です。

要素のない状態でgetFirst() getLast()を実行するとNoSuchElementExceptionがスローされます。
一方でlist.get(0) list.get(list.size()-1)を要素のない状態で実行するとIndexOutOfBoundsExceptionがスローされます。

reversed()で取得されるのはビューのため元のコレクションの順序を並び替えませんが、参照を保持しているため要素に変更を加えるとその変更が元のコレクションに反映される点には注意が必要です。

JavaDoc の コードスニペット(JEP 413)

Java 18 以降から JavaDoc コメントにて使用できる {@snippet}タグが導入されました。インラインと外部ファイル引用をサポートし、属性でハイライトやリージョン抽出などを制御できます。

詳細はOracle Help Center | JavaDocガイド > スニペットのプログラマーズ・ガイドをご参照ください。

以下のように使用します。

/**
* ユーザー登録処理の例
* {@snippet :
* User user = new User();
* user.setName("未来太郎");
* user.setEmail("mirai@example.com");
* userRepository.save(user);
* }
*/

以前は JavaDoc で API ドキュメントを作成する場合には、短い例や1行の例では{@code ...}、長い例の場合は<pre>{@code ...}</pre>のような構造体を使用して、ドキュメント・コメントにソース・コードのフラグメントを記載していました。

{@snippet}タグを使用するとスニペットのレンダリング出力には、コードブロックの左上隅に「クリップボードにコピー」ボタンが配置されるようになるため便利です。
また、言語の種類などの属性や強調表示なども指定できるようになるため、こちらの利用を推奨しています。

昨今の Java 開発での導入状況はどうかというと、JDK 標準ライブラリのドキュメントでも段階的に @snippet タグの利用が進められており、Java 21 時点では JDK の Javadoc においてコード例にコピー機能やシンタックスハイライトが付加されるケースが増えています(これらは@snippetタグによるものです)。

主要なJava Webアプリケーション向けOSSライブラリ内や拡張機能での利用状況に目を向けると、Spring Framework においては現時点でほとんど利用されていないものの、Jakarta EE の一部コンポーネントでは{@snippet}タグが公式ドキュメントに採用されています。特に Jakarta Persistence(JPA)では積極的に活用しており、アノテーション@Convertなどの Javadoc に複数行のコード例を{@snippet}タグで挿入しています。

Spring や Micronaut のように長年のコードとドキュメント資産を持つフレームワークでは、古いJavadoc構文からの移行に慎重な様子がうかがえます。一方で、新仕様に沿った Jakarta EE API や先進的な OSS では採用が始まっています。ツール面でもサポートが進んでおり、コード解析ツールの Checkstyle は2025年のバージョン 10.22.0 で Javadoc 内の@snippetタグ対応を行いました。

こういったツール側の追随や Java 23 で追加される Markdown 形式の JavaDoc コメントと共存可能であることも導入を後押ししている状況かと思います。
既存のコード資産の{@code}タグについては置き換えの検討を進めつつ、これからの新規実装箇所でまず利用するという方針が良いでしょう。

シールクラス(JEP 409)

シールクラスは継承可能な型を明示的に制約できるためコードの拡張範囲を強固にコントロールでき、不特定の継承を禁止して安全にするといった点や、継承関係の設計意図が宣言的に可視化されドキュメント代わりにもなるというメリットがありますが、一方で明確な方針無く利用するとコードの保守性や柔軟性が悪くなるというデメリットが目立ってしまいます。

シールクラスの利用については状況に応じて利用しないか、利用しても良い箇所について方針を決めた上で使用することとしています。
コーディング規約では以下のように補足しています。

【補足:シールクラス(sealed classes)とは】
Javaのsealedクラスは、継承できるサブクラスを明示的に制限する仕組みです。
これにより、ドメインモデルの制約強化やパターンマッチングの網羅性チェックが可能となり、意図しない拡張や誤用を防ぐことができます。
典型的な利用例としては、状態や種類が限定されるドメイン(例:イベント種別、計算式のノード型など)の表現や、パターンマッチング(switch文・式)で全ケースを網羅的に扱いたい場合などが挙げられます。
メリットは安全性・可読性の向上ですが、柔軟な拡張が難しくなるデメリットもあるため、利用方針を明確に定めてください。

最後に

Java 21 は過去のバージョンで追加された機能を強化するものが大半で、Java 17 のコーディング規約で追加された項目への追記が主な変更点となりました。

前回から引き続き、各プロジェクトや自分のチームで使用しないルールは、削除したり入れ替えたりして使うことを想定しているというスタンスは変わりません。
言語のアップデートを取り入れ、また作成済みのルールに関しても環境の変化に合わせて改善していきますので、適用するルールを取捨選択しながら活用いただければ幸いです。

9月にリリースが控えている次期 LTS バージョンの Java 25 に対応した Java コーディング規約についても準備を進めており公開予定ですので、そちらのアップデートにもご期待ください。