instanceof と型パターン
instanceof と型パターン とは
Java の
instanceof演算子と、Java 16 以降のパターンマッチングを学び、オブジェクトの実型に応じて分岐するコードを書いてみよう。
instanceof でオブジェクトの実型を見分ける
オブジェクト指向の Java では、変数の宣言型と、実際に入っているオブジェクトの型 (実型) が一致しないことがよくあります。たとえば Object o = "hello"; と書けば、宣言型は Object ですが、o の中身は String です。List<Object> items = new ArrayList<>(); のようなリストに String と Integer を混ぜて入れる場面でも、取り出した瞬間は「Object だけど中身は何かわからない」という状態になります。
このような場面で「中身が String だったらこう処理し、Integer だったらこう処理する」と書き分けるための演算子が instanceof です。o instanceof String のように書くと、o の実型が String であれば true、そうでなければ false を返します。if 文や switch 文と組み合わせれば、実型に応じた分岐が安全に書けるようになります。
instanceofは、Java のリフレクションやポリモーフィズムを活用する上で欠かせない演算子です。SpringやAndroidのフレームワークでも、EventやViewの実型を判定するために裏側でよく使われています。
本章では、instanceof の基本から始めて、Java 16 で導入された パターンマッチング 構文、さらに Java 21 で正式採用された switch 文での型パターンまで、段階的に紹介します。「中身を確認してからキャストする」という安全な書き方が、最新の Java ではどれだけ短く書けるようになったのかを体感してみてください。
instanceof 演算子の文法
instanceof の文法はとてもシンプルです。左側に判定したい変数や式、右側に型名を書きます。
Java
Object o = "hello";
boolean isString = o instanceof String; // true
boolean isInteger = o instanceof Integer; // falseinstanceof は boolean を返す演算子なので、if 文の条件に直接書けます。判定が真であれば、そのブロックの中では (String) o のようにキャストして使うのが従来の流儀でした。
Java
public static String classic(Object o) {
if (o instanceof String) {
String s = (String) o; // 古典的なキャスト
return "String:length=" + s.length();
}
if (o instanceof Integer) {
Integer n = (Integer) o; // ここでもキャストが必要
return "Integer:" + n;
}
return "Other";
}見てのとおり、instanceof で型を確認した直後にもう一度同じ型でキャストする、という二度手間が発生します。String と書く回数が多くなり、コピペミスで String の代わりに別の型を書いてしまうと ClassCastException の温床になります。
Java 15 以前ではこの書き方しか選択肢がなく、長年「冗長だ」「もっと短く書きたい」と言われ続けてきました。コードレビューでもキャスト忘れや型名の打ち間違いがよく指摘される箇所でした。
Java 16 のパターンマッチング if (o instanceof String s)
Java 16 で正式採用された機能が、instanceof の パターンマッチング です。型名のすぐ後ろに変数名を書くことで、判定とキャストを 1 行で済ませられます。
Java
public static String modern(Object o) {
if (o instanceof String s) {
return "String:length=" + s.length();
}
if (o instanceof Integer n) {
return "Integer:" + n;
}
return "Other";
}o instanceof String s の s は、if ブロックの中だけで使える String 型の変数です。コンパイラが自動で (String) o を入れてくれているイメージなので、明示的なキャストは不要で、誤って違う型でキャストするミスも防げます。
さらに && で繋げて条件を重ねることもできます。たとえば「String で、しかも空でない場合」を判定したいときは次のように書けます。
Java
if (o instanceof String s && !s.isEmpty()) {
System.out.println("non-empty string: " + s);
}s は instanceof 判定が真のときだけ使える変数なので、&& の右側でいきなり s.isEmpty() を呼んでも安全です。逆に || (or) で繋ぐ場合は、片方の条件が偽でも到達しうるため、変数 s は使えません。コンパイラがその辺りもちゃんと面倒を見てくれます。
パターンマッチングは Java 16 で
Standardになりましたが、プレビューとしては Java 14 から提供されていました。KotlinのisキーワードやScalaのmatch式に影響を受けた機能で、現代のJVM言語ではほぼ標準装備の発想です。
Java 21 の switch 型パターン
Java 21 では switch 文 (および switch 式) でも型パターンが使えるようになりました。if の連鎖よりも見やすく、網羅性のチェックも効きます。
Java
public static String describe(Object o) {
return switch (o) {
case String s -> "String:length=" + s.length();
case Integer n -> "Integer:" + n;
case null -> "Null";
default -> "Other";
};
}case String s -> のように書くと、o が String のときその枝に入り、変数 s で中身を受け取れます。case null を書けば null を別枠で扱えますし、default でその他を捕まえます。if を 5 個並べるよりも、ぐっと宣言的に書けるのが分かります。
sealed インタフェースと組み合わせると、コンパイラが「すべての型を網羅しているか」を検査してくれるので、default を書かなくても安全に分岐できます。これは関数型言語の 代数的データ型 に近い書き味で、最近の Java では大きな武器になります。
よくある間違い
instanceof を使うときに、初学者から中級者にかけてよく踏む落とし穴を 3 つ並べておきます。
instanceofとequalsを混同する —instanceofは「型が同じ (またはサブクラス) か」を見る演算子で、equalsは「値が等しいか」を見るメソッドです。"hello" instanceof Stringはtrueですが、"hello".equals("hello")もtrue、意味はまったく違います。型を見たいときはinstanceof、値を見たいときはequals、と覚えてくださいnullに対するinstanceofは常にfalse—null instanceof Stringも(Object) null instanceof Objectも、結果はすべてfalseです。これは Java の仕様で、nullには実型が存在しないためです。nullを扱いたい場合はif (o == null) { ... }のように別途分岐するか、Java 21 ならswitchのcase nullを使います- ダウンキャストの
ClassCastException—instanceofで確認せずにいきなり(String) oのようにキャストすると、実型が違っていれば実行時にClassCastExceptionが飛びます。パターンマッチングを使えばこのミスは構造的に防げるので、Java 16 以降のコードベースでは積極的に切り替えましょう
もう 1 つ補足すると、instanceof の右辺にはクラスやインタフェースしか書けません。int や double のような プリミティブ型 を直接置くとコンパイルエラーになります。整数判定がしたいなら Integer、小数判定なら Double のように、対応する ラッパー型 を指定してください。
instanceofの右辺にnullも書けません。意味的に「nullかどうか」を見たいときは、シンプルにo == nullを使うのが正解です。
やってみよう
今回の課題は、Solution.describeObject(Object o) を実装することです。動作仕様は次のとおりです。
oがStringなら、"String:length=" + ((String) o).length()を返す (たとえば"hello"なら"String:length=5")oがIntegerなら、"Integer:" + oを返す (たとえば42なら"Integer:42")- それ以外 (
Doubleやnullを含む) なら、"Other"を返す
書き方は自由ですが、おすすめは Java 16 のパターンマッチングです。if (o instanceof String s) { return "String:length=" + s.length(); } のように書けば、キャスト忘れの心配もなく、コードがぐっと読みやすくなります。
もし Java のバージョンが古くてパターンマッチングが使えない環境では、(String) o のように明示的にキャストする昔ながらの書き方でも構いません。どちらでも Solution.describeObject("hello") が "String:length=5" を返すようになれば合格です。
慣れてきたら、Long や Boolean を引数にしたときも個別に分岐するように拡張してみてください。switch 式で書き直すと、Java 21 の型パターンの威力が体感できます。instanceof を使いこなせるようになると、Object を引数に取る汎用メソッドや、Event 系のクラス階層を扱うコードがぐっと書きやすくなります。
よくある質問
Q. 中級の内容は実務でどれくらい使いますか?
A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。
Q. Stream と for ループはどっちで書くべき?
A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。
Q. 次のステップでは何を学ぶべきですか?
A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。
次のレッスン
次は ポリモーフィズムを活かす で、ポリモーフィズムを活かす を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- instanceof の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. instanceof とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
o instanceof Stringで String 判定をして、"String:length=" + 文字数を返すことo instanceof Integerで Integer 判定をして、"Integer:" + 値を返すこと- 上記いずれにも該当しない場合 (Double, null など) は
"Other"を返すこと
入出力例
test-cases.txt
describeObject("hello") → "String:length=5"
describeObject("") → "String:length=0"
describeObject(42) → "Integer:42"
describeObject(0) → "Integer:0"
describeObject(3.14) → "Other"