LoginSignup
58
52

More than 5 years have passed since last update.

JavaオレオレFrameworkに欠かせないリフレクションでの総称型解析

Last updated at Posted at 2014-12-12

総称型をリフレクションAPIから扱う場合に、例えばList<String> みたいな型のどこがどう取れるのか把握しにくかったのでまとめました。

興味のない人には退屈以外の何物でもない感じになってしまいました……

用語

  • Generics : 総称型に関連する言語仕様全体の名前
  • Type : 変数同士の代入可能性を決める「型」。Primitive Type と Reference Type に分かれる。
    • Reference Type は Class Type、Array Type、Interface Type に分かれる。
    • Class : いわゆるクラス。フィールド、メソッドをもち Object を生成できるもの。
  • Generic Type : 型パラメータ(Type Parameter)のついた型。例えば List<T>。Tが型パラメータ。
  • Parameterized Type : 型パラメータが具体的な型引数(Type Argument)になった型。例えば List<String>。String が型引数
  • Type Variable : List<T> の例でいうと T 単体をさす場合は Type Variable と呼ぶ。Generic Type で使われた時の呼び方が Type Parameter。
  • Raw Type : Generic Type や Parameterized Type から型パラメータを外したもの。例えば List

参考

Class クラス

おなじみの Class<T> クラスは、Type クラス(後述)のサブクラスです

  • インスタンスを生成するには Class<T> クラスのインスタンスが必要。
    • Type からでは生成できない。
  • プリミティブとラッパクラスは別の Class クラスになる。
  • 同一クラスの Class のインスタンス同士は同一オブジェクトなので実は == で同一判定できる
  • 配列は isArray() が true 。getComponentClass() で格納しているクラスの Class オブジェクトが取得できる
  • Class<T> 自体が型パラメータを持っていてややこしいが、実際の文脈では Class<?>と書いちゃうことも多そう。例えば以下のようなケース。
void handleObject(Object obj) {
   Class<?> clazz = obj.getClass();  // Object型で受け取ったけど……
   if (clazz.isArray()) {
      ... // 配列だった
   }

Type クラス

総称型を扱うには Type クラスの助けが必要になります。JDK8 API

  • Type クラスは Class のほかに4つのサブクラスを持つ
    • ParameterizedType
    • GenericArrayType
    • WildcardType
    • TypeVariable<D extends GenericDeclaration>

Type オブジェクトは、クラスオブジェクトClass、メソッドオブジェクトMethod の引数や戻り値や例外 から取得できます。

ただ、ランタイムのオブジェクトからは総称型の情報がなくなっていて取得できません。例えば obj.getClass()Class オブジェクトを取得してもそこから .getTypeParameter() では取得できない。

Type型取得の例
TypeVariable<Method>[] ptArray = method.getTypeParameters(); 
Type retType = method.getGenericReturnType(); 
TypeVariable<Class<X>> tvArray = clazz.getTypeParameter();

以降、各サブクラスをもう少し細かく見ていきます。

ParameterizedType

具体的な総称型とでもいえばよいのか。

  • pt.getActualTypeArguments() で型パラメータの配列がとれます。List<String>String の部分。
  • getRawType() で 型パラメータのない Raw な Class を取得できる
    • 返り値は Type で定義されているが 実際は Classinstanceof Class すると true になる。Class にならない場合はどのような時だろうか?
  • getOwnerType() で内部クラスの場合の親クラス(Type クラス)が取得できる。

GenericArrayType

T[] など総称型の配列を表す場合。 ちなみに List<String>[] のような配列は作れません。

  • getGenericComponentType() で中身の Type クラスが取得できます
    • 例えば T[] と宣言された型に対してこれを取得すると TType クラスが返ります

WildcardType

<? extends Object> のようなケースで使われる。

  • getLowerBounds()、getUpperBounds() で上限下限の Type クラスが取得できる

TypeVariable<D extends GenericDeclaration>

型変数。T などソース中の名前もとれる。
* D extends GenericDeclaration は、この型変数の提供元?をさすようだがちょっとよくわからない
* .getGenericDeclaration().getTypeParameters() でとれる
* getBounds() で 上限下限の Class の配列が取得できる。

TypeVariable の APIドキュメントの日本語訳はちょっと何言ってるかわからない。

結局何がどうなっているのか

test(X hoge) というメソッドに対して、「一つ目の引数の型」として Type pt = method.getParameterizedTypes()[0] を実行した際の X の内容ごとの ptその他の値を以下の表にまとめました。

X pt.getClass() pt.get- RawType() ←.getClass() pt.getActualType- Arguments()[0] ←.getClass()
String Class - - - -
List Class - - - -
List<?> ParameterizedType(*1) List Class ? WildcardType
List<String> ParameterizedType List Class String Class
List<List<String>> ParameterizedType List Class List<String> ParameterizedType
List<? extends Date> ParameterizedType List Class ? extends Date WildcardType

*1 実装クラスは sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl でした

X pt.getClass() pt.getCompnentType() ←.getClass() pt.getActualType- Arguments()[0] ←.getClass()
T TypeVariable - - - -
T[] GenericArrayType T TypeVariable - -

チェックに使ったコード

GenericsSample.java
package study;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class GenericsSample<T> {
    private static String typeToText(Type t) {
        return (t != null) ? (t +   " (" + t.getClass().getName() + ")") : "N/A";
    }
    public static void main(String[] args) {
        GenericsSample<Number> gpt = new GenericsSample<Number>();
        for (Method m : gpt.getClass().getMethods()) {
            if (!"test".equals(m.getName())) {
                continue;
            }
            Type[] types = m.getGenericParameterTypes();
            Class<?>[] classes = m.getParameterTypes();
            for (int i=0; i<classes.length; i++) {
                Type t = types[i];
                ParameterizedType gt = null;
                Type raw = null;
                Type owner = null;
                Type[] children = null;
                String childName = "";
                if (t instanceof ParameterizedType) {
                    gt = (ParameterizedType)t;
                    raw = gt.getRawType();
                    owner = gt.getOwnerType();
                    children = gt.getActualTypeArguments();
                    for (Type tt : children) {
                        childName = childName + 
                                "\n  type: " + typeToText(tt);
                    }
                }

                System.out.println("method:" + m.getName() + " param[" + i + "]:" +
                     "\n type: " + typeToText(t) + 
                     "\n class: " + classes[i] + 
                     "\n raw: "   + ((gt != null) ? typeToText(raw) : "N/A") +
                     "\n owner: " + ((gt != null) ? typeToText(owner) : "N/A") + 
                     childName +
                     ""
                );
            }
        }

    }

    private enum COLOR { RED, WHITE };

    public void test(
        String a0, 
        List a1,
        List<?> a2,
        List<String> a3,
        List<List<String>> a4,
        List<? extends String> a5,
        COLOR a6,
        List<COLOR> a7,
        T a8,
        T[] a9) {};

}

その他

Java8 で AnnotatedType というのが導入されていますが見なかったことにする

58
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
52