倭マン's BLOG

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

いまさら!? Class クラス (5) : クラス階層とインスタンス

Java の Class クラスに定義されているメソッドを見ていくシリーズ(目次)。 今回はクラス階層インスタンスに関連するメソッドを見ていきます。

クラス階層 Class Hierarchy

まずはクラス階層に関連するメソッド。 これらは返り値もしくは引数に Class オブジェクトが割り当てられているもの(の一部)です。 具体的に列挙すると下表のようになります(Class<T> に対するメソッド):

メソッド名 返り値 説明
getSuperclass()
getGenericSuperclass()
Class<? super T>
Type
スーパークラスを返す
getInterfaces()
getGenericInterfaces()
Class<?>
Type
実装インターフェース群を配列として返す
asSubclass(Class<U> clazz) <U> Class<? extends U> 引数のクラスがスーパークラスならこのクラス自身を返す
isAssignableFrom(Class<?> cls) boolean 引数のクラスがサブクラスなら true を返す

Type インターフェースの完全修飾名は java.lang.reflect.Type です(Java 5 にて導入)。 これは「型パラメータの情報を含んだ Class クラス」みたいなものだと思います。 以下で、各メソッドをもう少し具体的に見ていきましょう。 サンプルに用いたクラスのクラス図はこんな感じ:

スーパークラスを返すメソッド
まずはスーパークラスを返すメソッド。

  • getSuperclass()
  • getGenericSuperclass()

getGenericsSuperclass() メソッドは型パラメータの情報も含むので、ジェネリクスを用いたクラスをサンプルに使いましょう。 SuperClass, MyClass クラスを以下のように定義します:

public class SuperClass<N extends Number>{...}

public class MyClass<N extends Number> extends SuperClass<N>{...}

また、普通このメソッドをインターフェースなどに対して呼んだりはしないと思いますが、一応それらについての実行結果を載せておきます。

分類 getSuperclass() getGenericSuperclass()
クラス MyClass.class SuperClass.class SuperClass<N>*1
Object クラス Object.class null null
プリミティブ型 int.class null null
インターフェース MyInterface.class null null
アノテーション MyAnnotation.class null null
配列 String[].class Object.class Object.class
列挙 MyEnum.class Enum.class*2 Enum<MyEnum>
プロキシ MyProxy.getClass() Proxy.class*3 Proxy.class

配列と列挙型はそれぞれ Object クラス、Enum クラスを返すので、それらのインスタンスがオブジェクトとして扱われているのが分かりますね。 また、java.lang.reflect.Proxy クラスによって生成したプロキシクラスは全て Proxy クラスを継承しているようです。

実装インターフェースを返すメソッド
次は実装インターフェースを返すメソッド。 

  • getInterfaces()
  • getGenericInterfaces()

こちらもジェネリクスを用いたサンプルを書こうと思ったんですが、あまり違いがないので*4、ここではジェネリクスを用いないサンプルに対する実行結果を載せてあります:

分類 getInterfaces() getGenericInterfaces()
クラス MyClass.class [MyInterface.class] [MyInterface.class]
Object クラス Object.class [] []
プリミティブ型 int.class [] []
インターフェース MyInterface.class [MySuperInterface.class] [MySuperInterface.class]
アノテーション MyAnnotation.class [Annotation.class*5] [Annotation.class]
配列 String[].class [Cloneable.class,
 Serializable.class]
[Cloneable.class,
 Serializable.class]
列挙 MyEnum.class [] []
プロキシ MyProxy.getClass() [MyInterface.class] [MyInterface]

クラスは実装しているインターフェース、インターフェースは拡張しているインターフェースをそれぞれ返します。 注意が必要なのは、実装しているインターフェースまたは拡張している親クラスにさかのぼってインターフェースを検索しないことでしょうか。 前回見た「アノテーションの取得メソッド」の getDeclaredAnntations() メソッドみたいな感じです。 その意味では getDeclaredInterfaces() みたいな名前の方が良かったのかも知れませんね。

アノテーションと配列はそれぞれデフォルトで実装しているインターフェースがあるようです。 また、プロキシクラスはインターフェースを元にして作成した場合、そのインターフェースを実装するようです。

クラス階層を判定するメソッド
次はクラス階層を判定するメソッド。

  • asSubclass()
  • isAssginableFrom()

asSubclass() メソッド
まずは asSubclass() メソッド。 asSubclass() メソッドの挙動を大雑把に言うと「引数の(Class オブジェクトが表す)クラスがメソッドを呼ばれている(Class オブジェクトが表す)クラスのスーパークラスなら、メソッドを呼ばれた Class オブジェクトを返す」です。 上記の MyClass.class に対して asSubclass() メソッドを呼んだときの挙動は以下のようになります:

引数 挙動
SuperClass.class MyClass.class を返す
MyClass.class MyClass.class を返す
SubClass.class ClassCastException を投げる
継承関係のないクラス ClassCastException を投げる

サンプルコードはこんな感じ:

    assert MyClass.class.asSubclass(SuperClass.class) == MyClass.class;
    assert MyClass.class.asSubclass(MyInterface.class) == MyClass.class;
    assert MyClass.class.asSubclass(MyClass.class) == MyClass.class;

    try{
        MyClass.class.asSubclass(SubClass.class);
    catch(ClassCastException ex){
        assert false;
    }

例外を投げなかった場合、返り値のオブジェクトは常にメソッドを呼び出された Class オブジェクトになるようです。

isAssignableFrom() メソッド
次は isAssignableFrom() メソッド。 挙動を大雑把に言うと「引数の(Class オブジェクトが表す)クラスがメソッドを呼ばれている(Class オブジェクトが表す)クラスのサブクラスなら true を、そうでないなら false を返す」です。 上記の MyClass.class に対して isAssignable() メソッドを呼んだときの挙動は以下のようになります:

引数 返り値
SuperClass.class false
MyClass.class true
SubClass.class true
継承関係のないクラス false

「assignable(割当可能、代入可能)」かどうかを判定しているので、同じクラスなら true を返します。 サンプルコードはこんな感じ:

    SubClass sub = new SubClass();
    MyClass mine = sub;    // assignable !
    assert MyClass.class.isAssignableFrom(SubClass.class) == true;

    MyClass ours = mine;    // assignable !
    assert MyClass.class.isAssignableFrom(MyClass.class) == true;

    assert MyClass.class.isAssignableFrom(SuperClass.class) == false;
    assert MyClass.class.isAssignableFrom(MyInterface.class) == false;

インスタンス Instance

ここでは Class オブジェクトが表すクラスのインスタンスに関連するメソッドを見ていきます。 扱うクラスは下表の通り(Class<T> に対するメソッド ):

メソッド名 返り値
isInstance(Object obj) boolean
cast(Object obj) T
newInstance() T

isInstance() メソッド
isInstance() メソッドは instanceof 演算子と同じです。 ただし、instanceof 演算子は判定するクラス(型)がコードに埋め込まれるのに対して、isInstance() メソッドは対応する Class オブジェクトを変えれば実行中に判定するクラス(型)を変更することができます。

    SubClass submine = new SubClass();

    // instanceof による判定
    assert submine instanceof MyClass;

    // isInstance() による判定
    assert MyClass.class.isInstance(subMine);

cast() メソッド
cast() メソッドは () によるキャスト と同じです。 また、こちらも isInstance() メソッドと同じように、対応する Class オブジェクトを変えれば実行中にキャストするクラス(型)を変更することができるかもしれません*6

        SubClass submine = new SubClass();
        
        // () によるキャスト
        MyClass mine0 = (MyClass)submine;

        // Class リテラルから直接キャスト
        MyClass mine1 = MyClass.class.cast(submine);

        // Class オブジェクトを保持して、オブジェクトからキャスト (1)
        Class<MyClass> type2 = MyClass.class;
        MyClass mine2 = type2.cast(submine);

        // Class オブジェクトを保持して、オブジェクトからキャスト (2)
        Class<? extends MyClass> type3 = MyClass.class;
        MyClass mine3 = type3.cast(submine);

        // キャストを行う行でコンパイルエラー! type4 の型宣言がまずい
        Class<?> type4 = MyClass.class;
        MyClass mine4 = type4.cast(submine);

newInstance() メソッド
newInstance() メソッドは引数なしのコンストラクタを用いてインスタンスを生成するメソッドです:

    try{
        MyClass m = MyClass.class.newInstance();

    }catch(IllegalAccessException ex){
        assert false;
    }catch(InstantiationException ex){
        assert false;
    }

例外処理がチョット面倒。

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

*1:型パラメータの境界 Number がなくなってますが、これは返り値の Type オブジェクトを文字列として出力した際に表示されていなかっただけで、情報が失われたわけではないと思います。

*2:java.lang.Enum.class

*3:java.lang.reflect.Proxy.class

*4:型パラメータがあるインターフェースは、Class オブジェクトの代わりに対応する java.lang.reflect.Type オブジェクトが返されるだけ。

*5:java.lang.annotation.Annotation.class

*6:キャスト後のオブジェクトを受け取る変数の型を固定しないといけないので単純にはできなさそう。 ジェネリックなメソッドなどを使えばできそうだけど。