倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

いまさら!? Class クラス (2) : Class オブジェクトの取得方法

Java の Class クラスに定義されているメソッドを見ていくシリーズ(目次)。 Class オブジェクトに定義されているメソッドを使うためには、まず Class オブジェクトを何らかの方法で取得する必要があります。 ということで、今回は Class オブジェクトの取得方法を見ていきます。

Class オブジェクトの取得方法

Class オブジェクトを取得する方法には以下のようなものがあります:

  • クラスリテラルを指定する ・・・ .class
  • 既に存在するオブジェクトから、そのクラスを取得する ・・・ getClass()
  • クラスを表す文字列から、それが表すクラスを取得する ・・・ Class.forName()

以下、それぞれの方法を見ていきます。

class リテラルを指定する

まずはクラスリテラル*1を指定する方法。 これはクラスの名前が分かっている(決まっている)場合に使用します。 クラスリテラルの使用方法は《クラス名》.class です:

Class<String> clazz0 = String.class;
Class<?> clazz1 = String.class;
Class<Number> clazz2 = Integer.class;    // コンパイルエラー
Class<? extends Number> clazz3 = Integer.class;

クラスリテラルを使用した場合、用いた名前のクラスが必ずそのまま返されるので、3行目のように親子関係にあるクラス同士(Number と Integer)でもそのまま代入することはできません(Integer.class は Number.class に代入不可!)。 ただし、Class の型パラメータにワイルドカード (?) を指定すれば代入することができます。

プリミティブ型の場合
プリミティブ型を表す Class クラスも扱うことができます。 取得方法は次の2つです:

  • プリミティブ型のクラスリテラルを指定する
  • ラッパクラスに定義されている TYPE フィールド (static フィールド)を取得する

具体的には

Class<?> clazz4 = int.class;
Class<?> clazz5 = Double.TYPE

といった感じです。 その他の扱い方は通常の Class オブジェクトと同じです。

既に存在するオブジェクトから、そのクラスを取得する

次は、既に存在しているオブジェクトから Class オブジェクトを取得する方法です。 これはオブジェクトが存在している場合に用います(説明になってない?:->)。 取得方法は、Class を取得したいオブジェクトに対して getClass() メソッドを呼び出すだけです:

String s = "abc";
Class<? extends String> clazz = s.getClass()

getClass() メソッドの返り値
上記の例で Class オブジェクトを代入されている変数の型が Class<? extends String> であることに注意*2。 これは、getClass() メソッドが呼ばれている変数の型(正確には apparent type、宣言されている型)が、必ずしも実際の型 (actual type) と同じとは限らないためです。

もう少し具体的に見てみましょう。 Double クラスが Number クラスのサブクラス (class Double extends Number) であることを念頭に置いて、Number 変数を Double オブジェクトで初期化します:

Number n = new Double(1.0);

このとき、変数 n の「apparent type」は Number、「actual type」は Double です。 このとき、n に対して getClass() を呼び出すと「actual type」の Class オブジェクト、つまり Double.class が返されます。

ただし、普通のプログラムでは、変数に代入されているオブジェクトの「actual type」は一般に分からないことが多く*3、その場合、「この変数の actual type はこのクラス(今の場合 Number クラス)のサブタイプである」ということまでしか分かりません。 したがって、getClass() が返す Class オブジェクトは「apparent type」とワイルドカード (?) を用いて Class<? extends 《apparent type》> とする必要があります:

Class<? extends Number> clazz = n.getClass();

getClass() メソッドの他の使い方もいくつか載せておきます:

Number n = new Double(1.0);
Class<? extends Number> class0 = n.getClass();
Class<?> class1 = n.getClass();

Double d = new Double(1.0);
Class<? extends Double> clazz2 = d.getClass();
Class<? extends Number> clazz3 = d.getClass();        
Class<?> clazz4 = d.getClass();

getClass() の仕様
こちらのサイトによると、getClass() の仕様が JDK のバージョン (1.4〜1.6) によって変更になっているそうです:

1.4	public final Class getClass()
1.5	public final Class<? extends Object> getClass()
1.6	public final Class<?> getClass()

上記の話は1.5の定義をベースにしてますが、1.6でもコンパイルエラーになったりはしません。

クラスを表す文字列から、それが表すクラスを取得する

最後はクラスを表す文字列から、それが表すクラスを取得する方法*4。 この方法で Class オブジェクトを取得するには、Class クラスに定義されている static メソッド Class.forName() メソッドを使用します。 Class.forName() メソッドにはシグニチャの異なる2つのメソッドがオーバーロードされています:

メソッド名 返り値 チェック例外
forName(String className)
forName(String name, boolean initialize, ClassLoader loader)
Class<?> ClassNotFoundException

1つ目のメソッドは2つ目のメソッドを適当なパラメータで呼び出しているようです。 次の2つのメソッド呼び出しは同じ結果になります(name はクラス名とし、this キーワードでオブジェクトが参照できる場合*5

Class.forName(name);
Class.forName(name, true, this.getClass().getClassLoader());

Class.forName() メソッドの使用方法
Class.forName() メソッドの使用方法はこんな感じ:

Class<?> clazz = null
try{
    clazz = Class.forName("java.lang.String");

}catch(ClassNotFoundException ex){
    ex.printStackTrace();
}

指定するクラス名はパッケージ名まで含めた完全修飾名でなければなりません。

その他、いくつかの注意

  • 引数にプリミティブ型(int など)もしくは void を指定した場合、ClassNotFoundException が投げられます。
  • 引数に配列の型を指定した場合、配列要素のクラスはロードされるが、初期化はされません。 また、この場合に指定する名前(文字列)は、配列オブジェクトに対して Class#getName() で返される文字列にする必要があります。
// java.lang.String[] のクラス
Class<?> clazz1 = Class.forName("[Ljava.lang.String;")

// int[] のクラス
Class<?> clazz2 = Class.forName("[I")

プリミティブ型の配列も大丈夫なようです。

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

*1:リテラル」とは定数のことだと思ってください。 boolean リテラル「true, false」、数値リテラル「10, 1.0など」、文字リテラル「'a' など」、文字列リテラル「"abc" など」など。

*2:Class<?> でもいいんですが。

*3:「actual type」が分かるようなプログラムを書いてる人は「ポリモーフィズム」を勉強し直そう。

*4:以前はデータベースのドライバの初期化か何かで Class.forName() を書かないといけませんでしたが、JDK1.6で不必要になったようです。

*5:例えば static メソッド内などではこのようにはできません。