- JAVA
Java instanceofの使い方とパターンマッチング早見表
instanceofとは、「変数が指しているオブジェクトが、指定した型として扱えるか」を true / false で答える演算子です。 obj instanceof String のように書き、true のときだけ安全にキャストできます。Java 16 からは obj instanceof String s と書くだけで判定とキャストを一度に済ませる「パターンマッチング」が使え、Java 21 では switch 文や record の分解にも広がりました。
この記事では、同じ題材(図形の面積計算)を「従来のキャスト書き」「Java 16 のパターンマッチング」「Java 21 の record パターン」「Java 21 の switch パターン」の4通りで書き比べ、すべて OpenJDK 21.0.10 で動かした実行結果を載せています。null を渡したときの結果、パターン変数が使える範囲、&& と || での違い、無関係な型を書いたときのコンパイルエラーも実際に再現しました。コードはすべて1ファイルで完結しており、java ファイル名.java でそのまま試せます。
instanceofの早見表
まずは「書き方」と「結果」をまとめた早見表です。迷ったらこの表だけ見れば用が足ります。
書き方の早見表(バージョン別)
| やりたいこと | 書き方 | 使えるバージョン |
|---|---|---|
| 型を判定するだけ | obj instanceof String |
すべて |
| 判定してキャスト(従来) | if (obj instanceof String) { String s = (String) obj; } |
すべて |
| 判定と同時に変数へ入れる | if (obj instanceof String s) { ... } |
Java 16 以降 |
| 判定+追加条件 | if (obj instanceof String s && s.length() > 3) |
Java 16 以降 |
| 否定して早期 return | if (!(obj instanceof String s)) return; // この後で s が使える |
Java 16 以降 |
| record を分解して取り出す | if (obj instanceof Point(int x, int y)) |
Java 21 以降 |
| switch で型ごとに分岐 | switch (obj) { case String s -> ...; default -> ...; } |
Java 21 以降 |
| switch で型+条件 | case String s when s.isEmpty() -> ... |
Java 21 以降 |
| switch で null を受ける | case null -> ... |
Java 21 以降 |
使わない部分を _ で省く |
obj instanceof Point(int x, _) |
Java 22 以降 |
結果の早見表(Java 21 で確認)
| 式 | 結果 | 補足 |
|---|---|---|
null instanceof String |
false | null は必ず false。例外にもならない |
new Dog() instanceof Animal(Dog は Animal の子) |
true | 親クラスでも true |
new Dog() instanceof Puppy(Puppy は Dog の子) |
false | 子クラスには当てはまらない |
new Dog() instanceof Object |
true | null 以外は必ず true |
Integer の変数 instanceof String |
コンパイルエラー | 無関係な型は実行前に検出される |
obj instanceof String s || s.isEmpty() |
コンパイルエラー | || の右側では s が使えない |
obj instanceof List<String>(obj は Object) |
コンパイルエラー | 型引数は実行時に確認できない。List<?> と書く |
int の変数 instanceof Integer |
コンパイルエラー | 左側は参照型だけ(Java 21 時点) |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java instanceofの使い方とパターンマッチング早見表」
instanceofの基本|型を判定して true / false を得る
構文と読み方
変数 instanceof 型名
「左の変数が指すオブジェクトは、右の型として扱えますか?」という質問だと考えると読みやすくなります。扱える(=その型か、その子クラス、またはそのインターフェースを実装したクラス)なら true です。
親子関係のあるクラスと配列・コレクション・ラッパークラスで、結果を一通り確かめます。
import java.util.*;
public class Hier {
static class Animal {}
static class Dog extends Animal {}
static class Puppy extends Dog {}
interface Runner {}
public static void main(String[] args) {
Animal a = new Dog();
System.out.println("Dog instanceof Animal : " + (a instanceof Animal));
System.out.println("Dog instanceof Dog : " + (a instanceof Dog));
System.out.println("Dog instanceof Puppy : " + (a instanceof Puppy));
System.out.println("Dog instanceof Object : " + (a instanceof Object));
System.out.println("Dog instanceof Runner : " + (a instanceof Runner));
System.out.println("getClass()==Animal : " + (a.getClass() == Animal.class));
System.out.println("getClass()==Dog : " + (a.getClass() == Dog.class));
System.out.println("Animal.class.isInstance(a): " + Animal.class.isInstance(a));
Object arr = new int[]{1, 2};
System.out.println("int[] instanceof int[] : " + (arr instanceof int[]));
System.out.println("int[] instanceof Object[]: " + (arr instanceof Object[]));
Object strs = new String[]{"a"};
System.out.println("String[] instanceof Object[]: " + (strs instanceof Object[]));
Object list = new ArrayList<String>(List.of("a"));
System.out.println("ArrayList instanceof List<?>: " + (list instanceof List<?>));
if (list instanceof List<?> l && !l.isEmpty() && l.get(0) instanceof String first) {
System.out.println("先頭要素: " + first);
}
Object n = 42;
System.out.println("42 instanceof Integer: " + (n instanceof Integer));
System.out.println("42 instanceof Number : " + (n instanceof Number));
System.out.println("42 instanceof Long : " + (n instanceof Long));
}
}
実行結果(java Hier.java、OpenJDK 21.0.10):
Dog instanceof Animal : true
Dog instanceof Dog : true
Dog instanceof Puppy : false
Dog instanceof Object : true
Dog instanceof Runner : false
getClass()==Animal : false
getClass()==Dog : true
Animal.class.isInstance(a): true
int[] instanceof int[] : true
int[] instanceof Object[]: false
String[] instanceof Object[]: true
ArrayList instanceof List<?>: true
先頭要素: a
42 instanceof Integer: true
42 instanceof Number : true
42 instanceof Long : false
ここから読み取れるポイントは次のとおりです。
- 変数の宣言型(Animal)ではなく、実際に入っているオブジェクト(Dog)で判定される。
- 親クラス・実装しているインターフェースに対しても true になる。「ちょうどそのクラスか」を知りたいときは
getClass() == Dog.classを使う。 int[]はObject[]ではない(要素が基本型のため)。String[]はObject[]として true。Objectに入れた42はIntegerに自動で包まれる(オートボクシング)ので、IntegerとNumberには true、Longには false。
instanceof・getClass・isInstance の違い
| 方法 | 子クラスのとき | null のとき | 型を実行時に決められるか | 向いている場面 |
|---|---|---|---|---|
obj instanceof Dog |
true | false | 決められない(コードに直接書く) | ふだんの型判定 |
obj.getClass() == Dog.class |
false | NullPointerException | 決められる | 「ちょうどそのクラス」だけを通したいとき |
Dog.class.isInstance(obj) |
true | false | 決められる(Class 型の変数で渡せる) |
型を引数や設定で受け取るとき |
同じ題材で書き比べ|従来のキャストからswitchパターンまで
ここがこの記事の中心です。「円・長方形・正方形の面積を求める」という同じ処理を、4通りの書き方で並べます。
public class Compare {
sealed interface Shape permits Circle, Rect, Square {}
record Circle(double radius) implements Shape {}
record Rect(double width, double height) implements Shape {}
record Square(double side) implements Shape {}
// 1. 従来の書き方(全バージョン):判定 → キャスト → 取り出し
static double areaClassic(Object obj) {
if (obj instanceof Circle) {
Circle c = (Circle) obj;
return Math.PI * c.radius() * c.radius();
} else if (obj instanceof Rect) {
Rect r = (Rect) obj;
return r.width() * r.height();
} else if (obj instanceof Square) {
Square s = (Square) obj;
return s.side() * s.side();
}
throw new IllegalArgumentException("未対応: " + obj);
}
// 2. Java 16〜:instanceof のパターンマッチング(型パターン)
static double areaPattern(Object obj) {
if (obj instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
} else if (obj instanceof Rect r) {
return r.width() * r.height();
} else if (obj instanceof Square s) {
return s.side() * s.side();
}
throw new IllegalArgumentException("未対応: " + obj);
}
// 3. Java 21〜:instanceof の record パターン(分解まで一度に)
static double areaRecordPattern(Object obj) {
if (obj instanceof Circle(double radius)) {
return Math.PI * radius * radius;
} else if (obj instanceof Rect(double w, double h)) {
return w * h;
} else if (obj instanceof Square(var side)) {
return side * side;
}
throw new IllegalArgumentException("未対応: " + obj);
}
// 4. Java 21〜:switch のパターンマッチング(sealed なので default 不要)
static double areaSwitch(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rect r -> r.width() * r.height();
case Square s -> s.side() * s.side();
};
}
// 5. Java 21〜:switch + record パターン + when ガード
static String describe(Shape shape) {
return switch (shape) {
case Circle(double r) when r >= 10 -> "大きい円 半径=" + r;
case Circle(double r) -> "円 半径=" + r;
case Rect(double w, double h) when w == h -> "正方形に近い長方形 " + w;
case Rect(double w, double h) -> "長方形 " + w + "x" + h;
case Square(double side) -> "正方形 " + side;
};
}
public static void main(String[] args) {
Shape[] shapes = { new Circle(1), new Rect(2, 3), new Square(4), new Circle(10) };
for (Shape s : shapes) {
System.out.printf("%-34s classic=%.2f pattern=%.2f record=%.2f switch=%.2f%n",
s, areaClassic(s), areaPattern(s), areaRecordPattern(s), areaSwitch(s));
}
for (Shape s : shapes) {
System.out.println(describe(s));
}
}
}
実行結果(java Compare.java、OpenJDK 21.0.10):
Circle[radius=1.0] classic=3.14 pattern=3.14 record=3.14 switch=3.14
Rect[width=2.0, height=3.0] classic=6.00 pattern=6.00 record=6.00 switch=6.00
Square[side=4.0] classic=16.00 pattern=16.00 record=16.00 switch=16.00
Circle[radius=10.0] classic=314.16 pattern=314.16 record=314.16 switch=314.16
円 半径=1.0
長方形 2.0x3.0
正方形 4.0
大きい円 半径=10.0
4通りとも同じ結果になります。違いは「書く量」と「コンパイラが助けてくれる範囲」です。
| 書き方 | 1分岐あたりの行数 | 型名を書く回数 | 分岐の書きもれをコンパイラが検出 | 使えるバージョン |
|---|---|---|---|---|
| 1. 従来のキャスト | 3行(判定・キャスト・処理) | 3回(判定・変数の型・キャスト) | しない | すべて |
| 2. 型パターン | 2行 | 1回 | しない | Java 16 以降 |
| 3. record パターン | 2行(アクセサ呼び出しも不要) | 1回 | しない | Java 21 以降 |
| 4. switch パターン | 1行 | 1回 | する(sealed と組み合わせた場合) | Java 21 以降 |
1. 従来の書き方|判定してからキャストする
if (obj instanceof Circle) { Circle c = (Circle) obj; ... } は、すべてのバージョンで動く基本形です。Circle という型名を3回書くことになり、判定とキャストで別の型を書いてしまうと、実行時に ClassCastException になります。判定とキャストを必ずセットで書くのがこの形の約束ごとです。
2. Java 16以降|判定と同時に変数へ入れる(型パターン)
obj instanceof Circle c の Circle c の部分を型パターン、c をパターン変数と呼びます。判定が true のときだけ、キャスト済みの値が c に入ります。型名を書くのは1回だけなので、書き間違える場所が減ります。
3. Java 21以降|recordの中身まで一度に取り出す(recordパターン)
if (obj instanceof Rect(double w, double h)) {
return w * h; // r.width() と書かずに済む
}
record(Java 16 で入ったデータ用のクラス)であれば、型の判定と同時に中身を変数へ分解できます。Square(var side) のように var で型を推論させることもできます。入れ子の record もまとめて分解できます(後述)。
4. Java 21以降|switchで型ごとに分岐する
分岐が3つ以上あるなら switch が読みやすくなります。sealed interface(実装できるクラスを限定したインターフェース)と組み合わせると、default を書かなくても「全種類を扱っている」ことをコンパイラが確認してくれます。 試しに Square の case を消すと、次のコンパイルエラーになります。
ErrExhaust.java:6: エラー: switch式がすべての可能な入力値をカバーしていません
return switch (s) {
^
(英語表示では the switch expression does not cover all possible input values。)新しい図形を追加したときに、対応が必要な場所をコンパイラが教えてくれるわけです。
nullを渡すとどうなるか|instanceofはfalse、switchは例外
instanceof は、左側が null なら相手の型が何であっても false を返します。例外にはなりません。
public class NullCheck {
record Point(int x, int y) {}
public static void main(String[] args) {
Object obj = null;
String str = null;
System.out.println("null instanceof String : " + (obj instanceof String));
System.out.println("String変数(null) instanceof : " + (str instanceof String));
System.out.println("null instanceof String s : " + (obj instanceof String s));
System.out.println("null instanceof Point(..) : " + (obj instanceof Point(int x, int y)));
System.out.println("null instanceof Object : " + (obj instanceof Object));
// nullチェックを別に書かなくてよい
if (obj instanceof String s && !s.isEmpty()) {
System.out.println("文字列あり: " + s);
} else {
System.out.println("文字列ではない、または null");
}
// キャストは null でも通る(例外にならない)
String casted = (String) obj;
System.out.println("(String) null → " + casted);
// switch は case null がないと NullPointerException
try {
String r = switch (obj) {
case String s -> "String";
default -> "その他";
};
System.out.println(r);
} catch (NullPointerException e) {
System.out.println("switch(null) → " + e.getClass().getName());
}
String r2 = switch (obj) {
case null -> "nullです";
case String s -> "String";
default -> "その他";
};
System.out.println("case null あり → " + r2);
}
}
実行結果:
null instanceof String : false
String変数(null) instanceof : false
null instanceof String s : false
null instanceof Point(..) : false
null instanceof Object : false
文字列ではない、または null
(String) null → null
switch(null) → java.lang.NullPointerException
case null あり → nullです
このため、obj != null && obj instanceof String の obj != null は省略できます。 一方で switch は null を受け取ると例外になるので、null が来る可能性があるなら case null -> を1行足します。case null, default -> とまとめて書くこともできます。
パターン変数が使える範囲|「必ずマッチした」場所だけ
パターン変数のスコープ(使える範囲)は、ふつうの変数のように { } で決まるのではなく、「ここに来たなら必ずマッチしている」とコンパイラが判断できる場所で決まります。これをフロースコープと呼びます。
public class Scope {
static String early(Object obj) {
// 否定して早期 return すると、if の「後ろ」で s が使える
if (!(obj instanceof String s)) {
return "文字列ではない";
}
return "長さ=" + s.length();
}
public static void main(String[] args) {
Object obj = "hello";
// && の右側では使える
if (obj instanceof String s && s.length() > 3) {
System.out.println("1: " + s.toUpperCase());
}
// 三項演算子でも使える
int len = obj instanceof String s ? s.length() : -1;
System.out.println("2: " + len);
// 早期 return
System.out.println("3: " + early(obj));
System.out.println("4: " + early(123));
// while の条件でも使える
Object cur = "abc";
while (cur instanceof String s && !s.isEmpty()) {
System.out.println("5: " + s);
cur = s.substring(1);
}
// パターン変数は final ではない(再代入できる)
if (obj instanceof String s) {
s = s + "!";
System.out.println("6: " + s);
}
}
}
実行結果:
1: HELLO
2: 5
3: 長さ=5
4: 文字列ではない
5: abc
5: bc
5: c
6: hello!
| 場所 | パターン変数 s | 理由 |
|---|---|---|
if (obj instanceof String s) { ここ } |
使える | true のときだけ入る |
if (...) { } else { ここ } |
使えない | false のときに入る |
obj instanceof String s && ここ |
使える | 左が true のときだけ右が評価される |
obj instanceof String s || ここ |
使えない | 左が false のときに右が評価される |
obj instanceof String s ? ここ : 使えない |
? の直後だけ使える |
true 側だけ |
if (!(obj instanceof String s)) { return; } の後ろ |
使える | return しなかった=マッチしている |
if (!(obj instanceof String s)) { ここ } |
使えない | マッチしなかった側 |
| if 文を抜けた後(早期 return なし) | 使えない | マッチしたか決まらない |
「否定して早期 return すると、その後ろで変数が使える」のは、入力チェックを先頭にまとめる書き方と相性がよく、ネストを浅く保てます。なお、Java 16 の正式版からパターン変数は final ではなくなり、再代入もできます(6 の結果)。
コンパイルエラーになる書き方と直し方
実際に javac 21.0.10 でコンパイルして出たメッセージを載せます。日本語表示(javac -J-Duser.language=ja)と英語表示の両方を確認しました。
エラーメッセージ早見表
| 書き方 | 日本語のメッセージ | 英語のメッセージ |
|---|---|---|
obj instanceof String s || s.length() > 3 |
シンボルを見つけられません(変数 s) | cannot find symbol |
| if の外で s を使う | シンボルを見つけられません(変数 s) | cannot find symbol |
Integer の変数 instanceof String |
不適合な型: IntegerをStringに変換できません | incompatible types: Integer cannot be converted to String |
Object の変数 instanceof List<String> |
ObjectをList<String>に安全にキャストできません | Object cannot be safely cast to List<String> |
int の変数 instanceof Integer |
予期しない型(期待値: 参照、検出値: int) | unexpected type(required: reference, found: int) |
| 同じ名前の変数が既にある | 変数 sはすでにメソッド main(String[])で定義されています | variable s is already defined in method main(String[]) |
| switch で親の型を先に書く | 先行するcaseラベルが、このcaseラベルよりも優位です | this case label is dominated by a preceding case label |
| switch の分岐が足りない | switch式がすべての可能な入力値をカバーしていません | the switch expression does not cover all possible input values |
||(または)の右側ではパターン変数が使えない
public class ErrOr {
public static void main(String[] args) {
Object obj = "hello";
if (obj instanceof String s || s.length() > 3) {
System.out.println(s);
}
}
}
ErrOr.java:4: エラー: シンボルを見つけられません
if (obj instanceof String s || s.length() > 3) {
^
シンボル: 変数 s
場所: クラス ErrOr
ErrOr.java:5: エラー: シンボルを見つけられません
System.out.println(s);
^
シンボル: 変数 s
場所: クラス ErrOr
エラー2個
|| の右側は「左が false だったとき」に評価されます。つまり s に値が入っていない状況で使おうとしているので、コンパイラが止めてくれます。if の中身(5行目)も、「String ではなかったが右の条件で true になった」場合がありうるため s は使えません。
直し方:「String で、かつ長さが3より大きい」なら && にします。「String ではない、または空文字なら除外」としたいときは、否定形にまとめると s が使えます。
// 直した後:&& なら右側で使える
if (obj instanceof String s && s.length() > 3) { ... }
// 「String でない、または空」を除外したい場合
if (!(obj instanceof String s) || s.isEmpty()) {
return;
}
// ここでは s が使える(空でない String)
2つ目の形では、|| の右側が評価されるのは「左が false=String だった」ときなので、s が使えます。「どちらに進んだときにマッチが確定しているか」で決まると覚えておくと判断しやすくなります。
if の外でパターン変数を使う
ErrScope.java:7: エラー: シンボルを見つけられません
System.out.println(s.length());
^
シンボル: 変数 s
場所: クラス ErrScope
エラー1個
if ブロックを抜けた後は、マッチしたかどうかが決まらないので使えません。直し方は、処理を if の中に入れるか、前の章の「否定して早期 return」の形にすることです。
無関係な型を書くとコンパイルエラー
Integer num = 42;
if (num instanceof String) { ... } // 4行目
if (num instanceof String s) { ... } // 7行目
ErrUnrelated.java:4: エラー: 不適合な型: IntegerをStringに変換できません:
if (num instanceof String) {
^
ErrUnrelated.java:7: エラー: 不適合な型: IntegerをStringに変換できません:
if (num instanceof String s) {
^
エラー2個
Integer 型の変数に String が入ることはありえません。キャストできない組み合わせは instanceof でも書けない、というルールです。「常に false」ではなくコンパイルエラーになるので、書き間違いに早く気づけます。
直し方:変数の型を見直します。いろいろな型が入る想定なら、変数を Object(または共通の親クラス・インターフェース)で宣言します。
Object value = 42;
if (value instanceof String s) { ... } // コンパイルできる(結果は false)
なお、相手がインターフェースの場合は少し事情が変わります。
interface Runner {}
static class Animal {}
static final class Cat {}
Animal a = new Animal();
System.out.println(a instanceof Runner); // OK:Animal の子クラスが Runner を実装しているかもしれない
Cat c = new Cat();
System.out.println(c instanceof Runner); // エラー:final で子クラスを作れないので可能性がない
ErrFinalSealed.java:9: エラー: 不適合な型: CatをRunnerに変換できません:
System.out.println(c instanceof Runner); // エラー:final なので可能性がない
^
エラー1個
ジェネリクスの型引数は判定できない
Object obj = new ArrayList<String>();
if (obj instanceof List<String> list) { ... }
ErrGeneric.java:5: エラー: ObjectをList<String>に安全にキャストできません
Java のジェネリクスは、コンパイル後に <String> の情報が消える仕組み(型消去)です。実行時には「List かどうか」しかわからないため、Object から List<String> への判定は書けません。
直し方:List<?> で判定し、要素は取り出してから個別に判定します。
if (obj instanceof List<?> l && !l.isEmpty() && l.get(0) instanceof String first) {
System.out.println("先頭要素: " + first);
}
元の変数の型から型引数が確定できる場合は、そのまま書けます。次のコードは Java 21 でコンパイル・実行できました。
Collection<String> c = new ArrayList<String>();
if (c instanceof List<String> list) { // Collection<String> → List<String> は安全
System.out.println("List<String>: " + list);
}
基本型(int など)は左側に書けない
ErrPrim.java:4: エラー: 予期しない型
if (i instanceof Integer) {
^
期待値: 参照
検出値: int
(8行目の o instanceof int n も同じ「予期しない型」で、期待値は「クラスまたは配列」と表示されました。)
Java 21 では、instanceof の左側は参照型、右側はクラス・インターフェース・配列型に限られます。基本型をパターンで扱う機能は JEP 507(Primitive Types in Patterns, instanceof, and switch)として Java 25 で3回目のプレビューになっていることを OpenJDK の JEP ページで確認しました。プレビュー機能のため、この記事では動かしていません(未実行)。
「必ず当てはまる型」を書いたとき(Java 16〜20 と 21 の違い)
String str = "abc";
if (str instanceof CharSequence cs) { // String は必ず CharSequence
System.out.println("CharSequence: " + cs);
}
Java 21 ではコンパイル・実行できます。同じコードを javac --release 17 や --release 20 でコンパイルすると、次のエラーになりました。
OkGeneric.java:9: error: unconditional patterns in instanceof are not supported in -source 17
(use -source 21 or higher to enable unconditional patterns in instanceof)
Java 16〜20 では「必ずマッチするパターン」はエラー、Java 21 からは許可という違いです。Java 17 の環境で パターン付き instanceof がエラーになったときは、左側の変数の型が、右側の型の子クラスになっていないかを確認してみてください。
switch での順番エラー
return switch (o) {
case CharSequence cs -> "CharSequence";
case String s -> "String"; // ここに到達することがない
default -> "other";
};
ErrDominate.java:5: エラー: 先行するcaseラベルが、このcaseラベルよりも優位です
switch は上から順に調べるので、親の型を先に書くと、子の型の case には決して届きません。直し方は「具体的な型(子)を先、広い型(親)を後」に並べ替えることです。if-else の連鎖では同じ順番でもエラーにならず、そのまま動いてしまうため、switch にするとこの点もコンパイラに任せられます。
recordパターンの応用|入れ子の分解とジェネリクス
public class Nested {
record Point(int x, int y) {}
record Line(Point from, Point to) {}
record Box<T>(T value) {}
public static void main(String[] args) {
Object obj = new Line(new Point(0, 0), new Point(3, 4));
// 入れ子の record を一度に分解
if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
double len = Math.hypot(x2 - x1, y2 - y1);
System.out.println("長さ = " + len);
}
// 中身が null の場合、入れ子のパターンにはマッチしない
Object half = new Line(new Point(0, 0), null);
System.out.println("to が null: " + (half instanceof Line(Point p1, Point(var x, var y))));
System.out.println("型パターンなら: " + (half instanceof Line(Point p1, Point p2)));
// ジェネリックな record
Object box = new Box<>("本");
if (box instanceof Box<?>(String s)) {
System.out.println("Box の中身は文字列: " + s);
}
}
}
実行結果:
長さ = 5.0
to が null: false
型パターンなら: true
Box の中身は文字列: 本
- 入れ子の record は、コンストラクタと同じ形で書けば一度に分解できる。
- 入れ子の record パターン(
Point(var x, var y))は、その部分が null だとマッチしない。 - 一方、record パターンの中に置いた型パターン(
Point p2)は、コンポーネントの型と同じなら null でもマッチし、p2に null が入る。分解した変数を使う前に、null になりうるかを意識しておくと安心です。
Java 22 からは、使わない部分を _(アンダースコア)で省略できます(JEP 456)。Java 21 で obj instanceof P(int x, _) をコンパイルすると unnamed variables are a preview feature and are disabled by default. というメッセージになり、21 ではプレビュー扱いであることを確認しました。Java 22 以降での動作はこの記事では未実行です。
よく使う応用パターン
equals メソッドを短く書く
import java.util.Objects;
public class EqualsDemo {
static class Member {
private final String id;
private final String name;
Member(String id, String name) { this.id = id; this.name = name; }
@Override
public boolean equals(Object o) {
// null でも別の型でも false。キャストも不要
return o instanceof Member other
&& id.equals(other.id)
&& name.equals(other.name);
}
@Override
public int hashCode() { return Objects.hash(id, name); }
}
public static void main(String[] args) {
Member a = new Member("M001", "佐藤");
System.out.println(a.equals(new Member("M001", "佐藤")));
System.out.println(a.equals(new Member("M002", "鈴木")));
System.out.println(a.equals("M001"));
System.out.println(a.equals(null));
}
}
実行結果:
true
false
false
false
従来は「null チェック → 型チェック → キャスト → 比較」と4段階で書いていた equals が、o instanceof Member other && ... の1式にまとまります。null が false になる性質がそのまま活きる例です。
Object で受け取った値を型ごとに整形する
import java.util.List;
public class FormatDemo {
static String format(Object value) {
return switch (value) {
case null -> "(なし)";
case Integer i when i < 0 -> "マイナス " + (-i);
case Integer i -> "整数 " + i;
case String s when s.isBlank() -> "(空文字)";
case String s -> "文字列 " + s;
case List<?> l -> "リスト " + l.size() + "件";
default -> "その他 " + value.getClass().getSimpleName();
};
}
public static void main(String[] args) {
Object[] values = { 5, -3, "備品A", " ", List.of(1, 2, 3), 2.5, null };
for (Object v : values) {
System.out.println(format(v));
}
}
}
実行結果:
整数 5
マイナス 3
文字列 備品A
(空文字)
リスト 3件
その他 Double
(なし)
設定値や JSON を読み込んだ結果など、型がまちまちの値を受け取る入口で役立ちます。when のついた case を先、つかない case を後に並べるのがコツです。
instanceofを使うか、オーバーライドで書くかの判断表
型で分岐する方法は instanceof だけではありません。親クラスやインターフェースにメソッドを用意し、各クラスでオーバーライドする方法(ポリモーフィズム)もあります。
| 状況 | おすすめ | 理由 |
|---|---|---|
| 自分で作ったクラス群で、処理がそのクラスの責務 | メソッドのオーバーライド | 型を追加しても呼び出し側を変えずに済む |
| 種類が固定で、処理を1か所にまとめて見たい | sealed + switch パターン | 書きもれをコンパイラが検出。処理が一覧できる |
Object で受け取る(equals、汎用の受け口) |
instanceof パターン | 型がわからない入口では判定が必要 |
| 変更できないクラス(ライブラリの型など) | instanceof / switch パターン | メソッドを足せないので外側で分岐する |
| 分岐が1〜2個 | instanceof パターン | if のほうが短い |
| 分岐が3個以上 | switch パターン | 並びが揃って読みやすい |
| Java 8〜11 で書く | 従来のキャスト | パターンマッチングは Java 16 から |
flowchart TD
A[型によって処理を変えたい] --> B{そのクラスを自分で変更できる?}
B -- できない --> E{分岐は3個以上?}
B -- できる --> C{処理はそのクラス自身の仕事?}
C -- はい --> D[メソッドをオーバーライド]
C -- いいえ --> E
E -- はい --> F[sealed + switch パターン]
E -- いいえ --> G[instanceof パターン]
バージョン別対応早見表
| 機能 | 書き方の例 | 正式に使えるバージョン | 備考 |
|---|---|---|---|
| instanceof による型判定 | obj instanceof String |
すべて | |
| instanceof のパターンマッチング(型パターン) | obj instanceof String s |
Java 16(JEP 394) | Java 14 でプレビューとして登場 |
| 必ずマッチする型パターン | str instanceof CharSequence cs |
Java 21 | Java 16〜20 ではコンパイルエラー |
| switch のパターンマッチング | case String s -> |
Java 21(JEP 441) | when ガード、case null も同時に利用可 |
| record パターン | obj instanceof Point(int x, int y) |
Java 21(JEP 440) | 入れ子の分解、var も可 |
無名パターン _ |
obj instanceof Point(int x, _) |
Java 22(JEP 456) | Java 21 ではプレビュー |
| 基本型のパターン | obj instanceof int i |
未定(Java 25 時点で第3プレビュー、JEP 507) | この記事では未実行 |
手元の javac 21 で --release を切り替えて確認した結果は次のとおりです。
| コード | --release 11 | --release 16 | --release 17 | --release 21 |
|---|---|---|---|---|
o instanceof String s |
エラー | OK | OK | OK |
switch (o) { case String s -> ... } |
エラー | エラー | エラー | OK |
o instanceof P(int x, int y) |
エラー | エラー | エラー | OK |
Java 11 を指定したときのメッセージは次のとおりでした。
P16.java:1: エラー: instanceofでのパターンの一致は-source 11でサポートされていません
このメッセージが出たら、コードではなく JDK やビルド設定(--release、Maven の maven.compiler.release など)のバージョンを確認します。
従来の書き方からの書き換えチェックリスト
Java 16 以降へ移行するときに、機械的に書き換えられる形をまとめました。
| 直す前 | 直した後 |
|---|---|
if (o instanceof A) { A a = (A) o; ... } |
if (o instanceof A a) { ... } |
if (o != null && o instanceof A) |
if (o instanceof A) |
if (!(o instanceof A)) return false; A a = (A) o; |
if (!(o instanceof A a)) return false; |
if (o instanceof A && ((A) o).size() > 0) |
if (o instanceof A a && a.size() > 0) |
- [ ] パターン変数の名前が、同じメソッド内の既存の変数と重なっていないか(重なると
変数 sはすでに…定義されていますのエラー) - [ ]
||でつないだ条件の右側でパターン変数を使っていないか - [ ] switch に null が渡る可能性があるなら
case nullを足したか - [ ] switch の case は「具体的な型 → 広い型」の順に並んでいるか
- [ ] ジェネリクスは
List<?>のようにワイルドカードで判定しているか
よくある質問
Q. instanceof は null を渡すと例外になりますか?
A. なりません。左側が null のときは、右側の型が何であっても false を返します。そのため obj != null && obj instanceof String の null チェックは省略できます。パターンマッチング(obj instanceof String s)や record パターンでも同じく false です。switch に null を渡したときだけは NullPointerException になるので、case null を追加します。
Q. パターンマッチングは Java 何から使えますか?Java 8 や 11 では?
A. obj instanceof String s の形は Java 16 から正式に使えます。switch のパターンマッチングと record パターンは Java 21 からです。Java 8・11 では従来の「判定してからキャスト」の書き方を使います。javac 21 で --release 11 を指定すると「instanceofでのパターンの一致は-source 11でサポートされていません」というエラーになることを確認しました。
Q. instanceof と getClass() での比較はどう違いますか?
A. instanceof は子クラスのオブジェクトでも true になります。getClass() == Dog.class は「ちょうど Dog クラス」のときだけ true で、子クラスは false です。また getClass() は null に対して呼ぶと NullPointerException になります。ふだんの型判定は instanceof、厳密に同じクラスだけを通したいときは getClass() と使い分けます。
まとめ
- instanceof は「その型として扱えるか」を true / false で返す。親クラスやインターフェースに対しても true になる。
- null は必ず false。別途の null チェックは省略できる。switch に null を渡すときは
case nullを足す。 - Java 16 以降は
obj instanceof String sで判定とキャストを一度に。型名を書くのは1回で済む。 - パターン変数は「必ずマッチした」場所でだけ使える。
&&の右側は使える、||の右側は使えない、否定して早期 return した後ろは使える。 - 無関係な型(Integer と String など)やジェネリクスの型引数はコンパイルエラーになり、実行前に気づける。
- Java 21 では record パターンで中身の分解、switch パターンで型ごとの分岐ができ、sealed と組み合わせると分岐の書きもれをコンパイラが検出する。
- 自分で変更できるクラスならオーバーライド、型がわからない入口や変更できないクラスなら instanceof / switch、と使い分ける。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Java instanceofの使い方とパターンマッチング早見表」
株式会社RJCでは、Javaの型判定やパターンマッチングの書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。