1. 従来の instanceof の問題
少しノスタルジーから始めましょう。Java 16 以前は、オブジェクトの型を確認してからその型として使うには、instanceof のチェックと明示的キャストを組み合わせた冗長なパターンを書く必要がありました。
Object obj = ...; // 何らかのオブジェクト
if (obj instanceof String) {
String s = (String) obj;
System.out.println("文字列の長さ: " + s.length());
}
退屈だが頻出のパターン
このパターンは非常に頻繁に登場します — イベントハンドラ、異種コレクションの処理、継承を伴うビジネスロジックなど。
なぜ不便なのか
- コードの重複 — instanceof とキャストの両方で型を二重に書く必要があります。
- エラーのリスク — 誤った型にキャストして ClassCastException を引き起こしやすい。
- ノイズの多いコード — 複数のチェックがあると特に可読性が下がります。
2. Pattern Matching for instanceof: 新しい構文
Java 16 では instanceof にパターンマッチングが追加されました。これにより、型をチェックすると同時に、その型の変数を宣言し、if ブロック内で利用できます。
新しい構文
if (obj instanceof String s) {
// s はすでに文字列です!
System.out.println("文字列の長さ: " + s.length());
}
- instanceof の型の後ろに新しい変数名(String s)を書きます。
- 変数は条件が真のブロック内でのみ有効です。
- もう (String) のキャストを書く必要はありません — コンパイラが保証します。
どこが「魔法」? コンパイラが型をチェックし、合致すればその型の変数を「展開」します。そうでなければブロックは実行されません。
実際のコードではこう見える
Object obj = "こんにちは、Java 16+!";
if (obj instanceof String s) {
System.out.println("大文字: " + s.toUpperCase());
} else {
System.out.println("これは文字列ではありません!");
}
3. 安全性と可読性: これが「良い」と言える理由
ClassCastException を排除
Object obj = 123;
// String s = (String) obj; // ドカン!例外発生。
パターンマッチングでは、型チェックが成功した場合にのみ変数が作られるので、「誤った」キャストそのものが存在しません。
コードが短く、よりわかりやすくなる
2 行が 1 行になります。ノイズが減るほど読みやすく、保守もしやすくなります。
例: 複数の型を処理する
public static void printInfo(Object obj) {
if (obj instanceof String s) {
System.out.println("文字列の長さ: " + s.length());
} else if (obj instanceof Integer i) {
System.out.println("整数: " + (i + 1));
} else {
System.out.println("不明な型: " + obj);
}
}
4. 使用例と細かな注意点
複数の型を順にチェック
Object value = ...;
if (value instanceof String s) {
System.out.println("これは文字列です: " + s);
} else if (value instanceof Number n) {
System.out.println("これは数値です: " + n);
} else {
System.out.println("その他: " + value);
}
null と一緒に使う
オブジェクトが null の場合、instanceof は常に false を返します — NPE は起きませんが、ブロックは実行されません。
Object obj = null;
if (obj instanceof String s) {
// obj == null の場合、このブロックは決して実行されません
System.out.println("文字列: " + s);
} else {
System.out.println("obj は null か、文字列ではありません");
}
継承とパターンマッチング
class Animal {}
class Dog extends Animal {
void bark() { System.out.println("ワン!"); }
}
Animal a = new Dog();
if (a instanceof Dog d) {
d.bark(); // キャストなしで Dog のメソッドを呼べます!
}
確認の順序は重要です。より具体的な型からより一般的な型へ進めてください。そうしないと「狭い」判定が届かないことがあります。
比較: 従来と新しいアプローチ
| 従来の方法 | 新しい方法(pattern matching) |
|---|---|
|
|
5. instanceof に対するパターンマッチングの制約
- 変数のスコープ。 パターンで導入された変数は、判定が真のブロック内でのみ有効です。
if (obj instanceof String s) {
System.out.println(s); // s は参照可能
}
// System.out.println(s); // エラー!ここでは s は見えません
- 1 つの条件式内で異なる型の変数を複数宣言することはできません。
// コンパイルエラー:
if (obj instanceof String s || obj instanceof Integer i) {
// ...
}
- Java 16+ が必要。 より古い JDK バージョンでは新しい構文はサポートされません。
6. 実用例(簡単なアプリをベースに)
異なるタイプのタスクを持つ簡単な Task Manager を想定します。
クラス
class Task {
String title;
public Task(String title) { this.title = title; }
}
class BugTask extends Task {
int severity;
public BugTask(String title, int severity) {
super(title);
this.severity = severity;
}
}
class FeatureTask extends Task {
String feature;
public FeatureTask(String title, String feature) {
super(title);
this.feature = feature;
}
}
pattern matching を使った処理
public static void processTask(Task t) {
if (t instanceof BugTask bug) {
System.out.println("バグ: " + bug.title + ", 重大度: " + bug.severity);
} else if (t instanceof FeatureTask feature) {
System.out.println("機能: " + feature.title + ", モジュール: " + feature.feature);
} else if (t instanceof Task task) {
System.out.println("通常のタスク: " + task.title);
}
}
7. 役立つニュアンス
表: instanceof に対する Pattern Matching — 長所と短所
| 長所 | 短所/制限 |
|---|---|
| コード量が減り、可読性が向上 | Java 16+ が必要 |
| ClassCastException のリスクがない | 変数は if ブロック内でのみ可視 |
| コンパイル時の型安全性 | 1 つの条件で複数型には対応しない |
| 継承の処理に便利 | 古い IDE やビルドでは未対応の可能性 |
| 任意のクラスで利用可能 |
ビジュアル図解: instanceof の pattern matching はどう動くか
+---------------------------+
| Object obj |
+---------------------------+
|
v
if (obj instanceof Type t)
|
はい いいえ
(true) (false)
| |
v v
t は利用可能 t は存在しない
(コード) (アクセス不可)
8. instanceof の pattern matching でありがちな誤り
エラー1: if ブロックの外で変数を使おうとする。 パターンで変数を宣言しても、ブロックを抜ければその変数は消えます。コンパイラはもっともで、「s はどこにあるの?」と言って怒ります。
エラー2: instanceof が null でも真になると期待する。 オブジェクトが null の場合、instanceof は常に偽で、変数は作られません。必要なら null を別途処理しましょう。
エラー3: 古い JDK/IDE で新しい構文を使う。 Java 11 以下では instanceof のパターンマッチングは利用できず、構文エラーになります。JDK のバージョンを確認してください。
エラー4: スコープの勘違い。 パターンで導入された変数は、チェックが真の分岐内でのみ有効です。範囲外では存在しません。
エラー5: 一度に複数の型に対応できると期待する。 1 つの条件で異なる型の変数を同時に宣言することはできません: if (obj instanceof String s || obj instanceof Integer i) — これは不可。型ごとに個別のチェックを書いてください。
GO TO FULL VERSION