instanceof と型パターン

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

instanceof と型パターン とは

Java の instanceof 演算子と、Java 16 以降のパターンマッチングを学び、オブジェクトの実型に応じて分岐するコードを書いてみよう。

instanceof でオブジェクトの実型を見分ける

オブジェクト指向の Java では、変数の宣言型と、実際に入っているオブジェクトの型 (実型) が一致しないことがよくあります。たとえば Object o = "hello"; と書けば、宣言型は Object ですが、o の中身は String です。List<Object> items = new ArrayList<>(); のようなリストに StringInteger を混ぜて入れる場面でも、取り出した瞬間は「Object だけど中身は何かわからない」という状態になります。

このような場面で「中身が String だったらこう処理し、Integer だったらこう処理する」と書き分けるための演算子が instanceof です。o instanceof String のように書くと、o の実型が String であれば true、そうでなければ false を返します。if 文や switch 文と組み合わせれば、実型に応じた分岐が安全に書けるようになります。

instanceof は、Java の リフレクションポリモーフィズム を活用する上で欠かせない演算子です。SpringAndroid のフレームワークでも、EventView の実型を判定するために裏側でよく使われています。

本章では、instanceof の基本から始めて、Java 16 で導入された パターンマッチング 構文、さらに Java 21 で正式採用された switch 文での型パターンまで、段階的に紹介します。「中身を確認してからキャストする」という安全な書き方が、最新の Java ではどれだけ短く書けるようになったのかを体感してみてください。

diagram (will load when visible)

instanceof 演算子の文法

instanceof の文法はとてもシンプルです。左側に判定したい変数や式、右側に型名を書きます。

Java

Object o = "hello"; boolean isString = o instanceof String; // true boolean isInteger = o instanceof Integer; // false

instanceofboolean を返す演算子なので、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 ss は、if ブロックの中だけで使える String 型の変数です。コンパイラが自動で (String) o を入れてくれているイメージなので、明示的なキャストは不要で、誤って違う型でキャストするミスも防げます。

さらに && で繋げて条件を重ねることもできます。たとえば「String で、しかも空でない場合」を判定したいときは次のように書けます。

Java

if (o instanceof String s && !s.isEmpty()) { System.out.println("non-empty string: " + s); }

sinstanceof 判定が真のときだけ使える変数なので、&& の右側でいきなり s.isEmpty() を呼んでも安全です。逆に || (or) で繋ぐ場合は、片方の条件が偽でも到達しうるため、変数 s は使えません。コンパイラがその辺りもちゃんと面倒を見てくれます。

パターンマッチングは Java 16 で Standard になりましたが、プレビューとしては Java 14 から提供されていました。Kotlinis キーワードや Scalamatch 式に影響を受けた機能で、現代の 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 -> のように書くと、oString のときその枝に入り、変数 s で中身を受け取れます。case null を書けば null を別枠で扱えますし、default でその他を捕まえます。if を 5 個並べるよりも、ぐっと宣言的に書けるのが分かります。

diagram (will load when visible)

sealed インタフェースと組み合わせると、コンパイラが「すべての型を網羅しているか」を検査してくれるので、default を書かなくても安全に分岐できます。これは関数型言語の 代数的データ型 に近い書き味で、最近の Java では大きな武器になります。

よくある間違い

instanceof を使うときに、初学者から中級者にかけてよく踏む落とし穴を 3 つ並べておきます。

  • instanceofequals を混同するinstanceof は「型が同じ (またはサブクラス) か」を見る演算子で、equals は「値が等しいか」を見るメソッドです。"hello" instanceof Stringtrue ですが、"hello".equals("hello")true、意味はまったく違います。型を見たいときは instanceof、値を見たいときは equals、と覚えてください
  • null に対する instanceof は常に falsenull instanceof String(Object) null instanceof Object も、結果はすべて false です。これは Java の仕様で、null には実型が存在しないためです。null を扱いたい場合は if (o == null) { ... } のように別途分岐するか、Java 21 なら switchcase null を使います
  • ダウンキャストの ClassCastExceptioninstanceof で確認せずにいきなり (String) o のようにキャストすると、実型が違っていれば実行時に ClassCastException が飛びます。パターンマッチングを使えばこのミスは構造的に防げるので、Java 16 以降のコードベースでは積極的に切り替えましょう

もう 1 つ補足すると、instanceof の右辺にはクラスやインタフェースしか書けません。intdouble のような プリミティブ型 を直接置くとコンパイルエラーになります。整数判定がしたいなら Integer、小数判定なら Double のように、対応する ラッパー型 を指定してください。

instanceof の右辺に null も書けません。意味的に「null かどうか」を見たいときは、シンプルに o == null を使うのが正解です。

やってみよう

今回の課題は、Solution.describeObject(Object o) を実装することです。動作仕様は次のとおりです。

  1. oString なら、"String:length=" + ((String) o).length() を返す (たとえば "hello" なら "String:length=5")
  2. oInteger なら、"Integer:" + o を返す (たとえば 42 なら "Integer:42")
  3. それ以外 (Doublenull を含む) なら、"Other" を返す

書き方は自由ですが、おすすめは Java 16 のパターンマッチングです。if (o instanceof String s) { return "String:length=" + s.length(); } のように書けば、キャスト忘れの心配もなく、コードがぐっと読みやすくなります。

もし Java のバージョンが古くてパターンマッチングが使えない環境では、(String) o のように明示的にキャストする昔ながらの書き方でも構いません。どちらでも Solution.describeObject("hello")"String:length=5" を返すようになれば合格です。

慣れてきたら、LongBoolean を引数にしたときも個別に分岐するように拡張してみてください。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 つができることを確認しましょう。

  1. instanceof の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. instanceof とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. o instanceof String で String 判定をして、"String:length=" + 文字数 を返すこと
  2. o instanceof Integer で Integer 判定をして、"Integer:" + 値 を返すこと
  3. 上記いずれにも該当しない場合 (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"

ヒント

main.java
main.java
学習モード

メモ

instanceof と型パターン

⌘S で保存