はじめに
Java で日常的に使う構文のひとつに instanceof があります。
オブジェクトの型を調べて安全にキャストするために利用されますが、従来の Java では「型判定」と「キャスト」を必ずセットで書かなければならず、冗長になりがちでした。
Object obj = ...;
if (obj instanceof MUser) {
MUser muser = (MUser) obj; // 明示的なキャストが必須
System.out.println(muser.getId());
}
この「二度書き」が地味にストレス…と感じたことはないでしょうか?
そんな悩みを解決するのが、Java 16 で導入された パターンマッチ for instanceof、
通称パターン変数です。
パターン変数とは?
一言で言えば、
instanceof の型判定とキャストを同時に行える仕組み
判定が真であれば、自動的に変数が宣言され、そのスコープで安全に使えるようになります。
基本の書き方
// 従来の書き方
if (obj instanceof MUser) {
MUser muser = (MUser) obj;
System.out.println(muser.getId());
}
// パターン変数を使った書き方
if (obj instanceof MUser muser) {
System.out.println(muser.getId());
}
これだけでキャストの手間がなくなり、コードがすっきりします。
muser が「パターン変数」です。
スコープ
パターン変数は if のブロック内でのみ有効です。
例えば次のようなコードはコンパイルエラーになります。
if (obj instanceof MUser muser) {
System.out.println(muser.getId());
}
System.out.println(muser.getId()); // ← ここでは使えない!
これは、あくまで「条件が真のときだけ有効な変数」だからです。
可読性と安全性の両立を意識した仕様と言えます。
条件式との組み合わせ
&& や || といった論理演算子とも自然に組み合わせられます。
if (obj instanceof MUser muser && muser.getId() != null) {
System.out.println("User id = " + muser.getId());
}
このように書くと「MUser型で、かつ id が null でない場合」という条件を簡潔に表現できます。
従来ならキャストを一度してから改めて条件を書く必要がありましたが、その冗長さが消えています。
else との連携
else に入った場合はパターン変数は存在しません。
つまり「型が違った場合に別の処理をする」というコードも明快に書けます。
if (obj instanceof MUser muser) {
System.out.println("User: " + muser.getId());
} else {
System.out.println("Not a MUser");
}
switch とパターンマッチ
Java 21 では switch 文に対するパターンマッチも標準化されました。
これにより、複数の型をスマートに分岐できます。
switch (obj) {
case MUser muser -> System.out.println("User id: " + muser.getId());
case MGroup mgroup -> System.out.println("Group id: " + mgroup.getId());
default -> System.out.println("Unknown type");
}
従来の instanceof + else if のネストから解放され、コードの見通しが大幅に改善されます。
実務でのメリット
パターン変数の利点は単なる「省略記法」以上に、バグを減らす効果にあります。
- キャストの書き忘れがなくなる
ClassCastException が発生するリスクをなくせます。 - ネストが浅くなる
キャスト後の変数をスコープ外に出す必要がないため、コードが自然に整理されます。 - 可読性が向上
「型チェック → 安全に利用」という流れが一目で理解でき、レビューもしやすいです。
既存コードのリファクタリング例
例えば AOP で取得した引数から共通して id を抜きたいとしましょう。
従来ならこう書きがちです。
Object arg = jp.getArgs()[0];
if (arg instanceof MUser) {
MUser muser = (MUser) arg;
Integer id = muser.getId();
System.out.println("User id = " + id);
} else if (arg instanceof MGroup) {
MGroup mgroup = (MGroup) arg;
Integer id = mgroup.getId();
System.out.println("Group id = " + id);
}
これをパターン変数で書くと:
Object arg = jp.getArgs()[0];
if (arg instanceof MUser muser) {
System.out.println("User id = " + muser.getId());
} else if (arg instanceof MGroup mgroup) {
System.out.println("Group id = " + mgroup.getId());
}
同じロジックでもコード量がぐっと減り、すっきりしました。
注意点
- パターン変数はスコープが限定されるので、外で使いたい場合は別途変数に代入する必要がある
- null はパターンマッチにマッチしない(obj が null なら instanceof 自体が false)
まとめ
Java も着実に進化していて、今まで「冗長だな」と感じていた部分が解消されつつあります。
既存コードでも無理なく取り入れられるので、リファクタリング時に積極的に活用してみると良いでしょう。