HOWTO · Java

Javaでオブジェクトの実行時クラスを確認する方法:getClass()、getSimpleName()、getTypeName()

Javaでnullではないオブジェクトの実行時クラスを調べる方法を解説します。getClass()、getSimpleName()、getTypeName()の使い方と、変数の宣言型との違いを紹介します。

このページの内容

getClass() を使うと、null ではないオブジェクトの実行時クラスを調べられます。変数の宣言型が分かるわけではありません。パッケージなしのクラス名が必要なら getSimpleName()、パッケージ修飾名が必要なら getName()、java.lang.String のような型名が必要なら getTypeName() を呼び出します。名前ではなく「はい/いいえ」の判定だけが必要な場合は、instanceof 演算子を使います。

getClass().getSimpleName() を使って Java で変数の型を調べる

オブジェクトに対して getClass() を呼び出して Class<?> オブジェクトを取得し、続けて getSimpleName() を呼び出すと、パッケージ情報を含まないクラス名が得られます。変数が Object として宣言されていても、結果はオブジェクトの実行時クラスを反映します。

この例を実行してください:

public class RuntimeSimpleNames {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String[] names = {"Ada", "Lin"};
    int count = 3;
    Object boxedCount = count;

    System.out.println(value.getClass().getSimpleName());
    System.out.println(names.getClass().getSimpleName());
    System.out.println(boxedCount.getClass().getSimpleName());
  }
}

出力:

String
String[]
Integer

配列はオブジェクトなので、getClass() は配列にも使えます。String[] 型の値の単純名は String[] です。int のようなプリミティブには getClass() メソッドがありません。その理由と、ボクシングが代わりに何を返すかは、後述のプリミティブの節で説明します。

getClass().getName() を使って Java で変数の型を調べる

getName() は、パッケージを含む実行時クラスの完全修飾名を返します。同名クラスを区別するためにパッケージが必要なときに使います。ただし配列については、読みやすいソース構文ではなく JVM 内部の名前を返します。先頭の [ は配列の次元を 1 つずつ表し、L<クラス>; が要素の型を符号化します。

この例を実行してください:

public class RuntimeName {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String[] names = {"Ada", "Lin"};

    System.out.println(value.getClass().getName());
    System.out.println(names.getClass().getName());
  }
}

出力:

java.lang.String
[Ljava.lang.String;

2 行目は java.lang.String の 1 次元配列を表しています。この符号化は読みにくいため、配列には getSimpleName() か getTypeName() の方が適しています。

getClass().getTypeName() を使って Java で変数の型を調べる

getTypeName() は型名を String として返します。String オブジェクトの場合、getSimpleName() と違ってパッケージ名も含まれます。

この例を実行してください:

public class RuntimeTypeName {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String typeName = value.getClass().getTypeName();
    System.out.println(typeName);
  }
}

出力:

java.lang.String

短い名前で十分なら getSimpleName()、パッケージ修飾のクラス名が必要なら getName()、配列も読みやすく表示される型名が欲しいなら getTypeName() を選びます。いずれのメソッドも、変数の宣言型を返すことはありません。

instanceof を使ってオブジェクトがある型のインスタンスか調べる

instanceof 演算子は、名前を返す代わりに「はい/いいえ」の問いに答えます。このオブジェクトは、指定した型またはそのサブタイプのインスタンスか、という判定です。そのため型階層全体に従います。一方、getClass() を == で比較すると、正確に 1 つの実行時クラスだけを判定します。

この例を実行してください:

import java.util.ArrayList;
import java.util.List;

public class InstanceofCheck {
  public static void main(String[] args) {
    Object value = new ArrayList<String>();

    System.out.println(value instanceof ArrayList);
    System.out.println(value instanceof List);
    System.out.println(value instanceof String);
    System.out.println(value.getClass() == ArrayList.class);
    System.out.println(value.getClass() == List.class);
  }
}

出力:

true
true
false
true
false

このオブジェクトは ArrayList のインスタンスであり、同時に ArrayList が実装する List インターフェースのインスタンスでもあるため、両方の instanceof 判定が true になります。最後の比較は、正確な実行時クラスが List ではなく ArrayList であるため false です。目安として、instanceof はサブタイプを受け入れ、getClass() の等価比較は正確なクラスだけを受け入れます。

instanceof のパターンマッチングで判定とキャストを 1 ステップで行う

Java 16 以降、instanceof は判定する型のパターン変数を宣言できます。判定が成功すると、その変数はすでにキャスト済みであり、判定が成功したと分かっている範囲でのみ有効です。以前のコードで必要だった個別のキャストは不要になります。

この例を実行してください:

public class InstanceofPattern {
  public static void main(String[] args) {
    Object value = "Hello, Java!";

    if (value instanceof String text) {
      System.out.println(text.toUpperCase());
    }

    Object number = 42;
    if (number instanceof String text) {
      System.out.println(text);
    } else {
      System.out.println("Not a string");
    }
  }
}

出力:

HELLO, JAVA!
Not a string

最初の判定では text がそのまま使える String として束縛されます。2 番目の判定は、ボクシングされた 42 が Integer であるため失敗し、else 側が実行されます。

null の可能性がある変数の型を調べる

null 参照はどのオブジェクトも指していないため、実行時クラスを持ちません。それに対して getClass() を呼び出すと NullPointerException がスローされます。instanceof 演算子は null に対して安全な判定で、例外をスローせず、単に false を返します。

この例を実行してください:

public class NullTypeCheck {
  public static void main(String[] args) {
    Object value = null;

    System.out.println(value instanceof String);

    try {
      System.out.println(value.getClass().getSimpleName());
    } catch (NullPointerException e) {
      System.out.println("getClass() failed: value is null");
    }
  }
}

出力:

false
getClass() failed: value is null

null の可能性がある値については、getClass() のメソッドを呼び出す前に、明示的な value == null チェックか instanceof 判定で保護してください。

Java でプリミティブ値の型を調べる

int や double のようなプリミティブ値は、オブジェクトではなく単なる値です。メソッドを持たず、実行時クラスも持ちません。その型はコンパイル時に固定され、実行時に JVM が扱うのは生の値だけです。count.getClass() がコンパイルできないのはそのためです。

実行時に調べるには、ラッパークラスへボクシングさせます。返される型はそのラッパーであり、プリミティブ自体になることは決してありません。プリミティブ型そのものに対するクラスリテラルは存在し、int.class は Integer.TYPE と同じオブジェクトですが、それが名付けるのは抽象的な型であって、特定の値ではありません。

この例を実行してください:

public class PrimitiveTypeCheck {
  public static void main(String[] args) {
    int count = 3;
    double price = 9.99;

    Object boxedCount = count;
    Object boxedPrice = price;

    System.out.println(boxedCount.getClass().getSimpleName());
    System.out.println(boxedPrice.getClass().getSimpleName());
    System.out.println(int.class.getSimpleName());
    System.out.println(double.class == Double.TYPE);
  }
}

出力:

Integer
Double
int
true

要するに、プリミティブの型はコンパイル時だけの事実です。ボクシングによってオブジェクトが生まれるまでは、実行時に getClass() や instanceof が調べられるものは何も存在しません。

まとめ

オブジェクトの正確な実行時クラスの名前が必要な場合は、getClass().getSimpleName()、getClass().getName()、getClass().getTypeName() のいずれかを使います。サブタイプでも判定を通したい場合は instanceof を使い、キャストも必要な場合は Java 16 以降のパターン変数を併用します。null の可能性がある値には instanceof が安全です。プリミティブ値には報告すべき実行時型がありません。先にボクシングし、int に対する Integer のようなラッパークラスが返ることを想定してください。