CLOVER🍀

That was when it all began.

リフレクションで、ジェネリクスの情報にアクセスする

Infinispanでもうちょっと遊ぶ予定だったのですが、オフィシャルサイトのWikiがなんか途中で落ちてしまったので、別のネタで遊ぶことにしました。
*このネタがだいたい終わった頃には、復旧していましたが…

今回は、パラメータ化された型に関する情報をリフレクションを使ってアクセスしてみようと思います。ちょっと前からやってみたかったのです。

ジェネリクスの情報は、ClassクラスやField、Methodなどからアクセスできるのですが、それぞれ以下のような方法で取得できます。

クラス メソッド 取得できる情報
Class getTypeParameters クラス定義において宣言された型変数の情報
Field getGenericType フィールドに適用された型変数の情報
Constructor getGenericParameterTypes コンストラクタのパラメータに関する型変数の情報
Constructor getGenericExceptionTypes コンストラクタのthrowsに宣言されている、例外に関する型変数の情報
Method getGenericParameterTypes メソッドのパラメータに関する型変数の情報
Method getGenericReturnType メソッドの戻り値の型に関する型変数の情報
Method getGenericExceptionTypes メソッドのthrowsに宣言されている、例外に関する型変数の情報

これらのメソッドから取得できるのは、すべてjava.lang.reflect.Typeインターフェースのサブクラス、またはサブインターフェースになります。

クラスまたはインターフェース名 意味
java.lang.Class 普通のClassクラスです パラメータ化されてないクラス、フィールドなど
java.lang.reflect.TypeVariable 型変数自体の情報 T field、List listなどのT
java.lang.reflect.ParameterizedType パラメータ化された型 List、Listなどの、List型のこと
java.lang.reflect.WildcardType ワイルドカードを持った型 List<? extends Number>などの<? 〜>の部分
java.lang.reflect.GenericArrayType 型変数情報を使って宣言された配列 T、List

上記のインターフェースから、パラメータ化された型に適用された型情報や、型変数を宣言したクラス、ワイルドカードの上限/下限などを取得することができます。

サンプルを作ってみたので、そちらを。

型情報を取得される側のクラス。

class ParameterizedClass<T> {
    public String nonGeneric;
    public T field;
    public T[] array;
    public List<String> strings;
    public List<T> list;
    public List<T>[] listArray;
    public List<? extends Number> wildUnderNumbers;
    public List<? super Number> wildUpperNumbers;

    public <A, B> void m1(A a, B b) { }
    public <A extends Number> A m2() { return null; };
    public <A extends java.io.IOException, B extends RuntimeException> void m3() throws A, B { }
    public <A> void m4(List<? extends A> list) { }
}

class NonGeneric extends ParameterizedClass<Object> { }
class Actual<String> extends ParameterizedClass<String> { }
class ParameterizedClass2<T extends Number> { }

型情報を出力するプログラム。

import java.lang.reflect.*;

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

public class ParameterizedTypeExample {
    public static void main(String[] args) {
        try {
            printf("-------------------- Print ParameterizedClass --------------------%n");
            printGenerics(ParameterizedClass.class);
            printf("-------------------- Print ParameterizedClass --------------------%n");
            printf("%n%n");

            printf("-------------------- Print nonGeneric --------------------%n");
            printGenerics(ParameterizedClass.class.getField("nonGeneric"));
            printf("-------------------- Print nonGeneric --------------------%n");
            printf("%n%n");

            printf("-------------------- Print field --------------------%n");
            //printf("Field#getType => %s%n",
            //       ParameterizedClass.class.getField("field").getType().getName());
            printGenerics(ParameterizedClass.class.getField("field"));
            printf("-------------------- Print field --------------------%n");
            printf("%n%n");

            printf("-------------------- Print array --------------------%n");
            printGenerics(ParameterizedClass.class.getField("array"));
            printf("-------------------- Print array --------------------%n");
            printf("%n%n");

            printf("-------------------- Print strings --------------------%n");
            printGenerics(ParameterizedClass.class.getField("strings"));
            printf("-------------------- Print strings --------------------%n");
            printf("%n%n");

            printf("-------------------- Print list --------------------%n");
            printGenerics(ParameterizedClass.class.getField("list"));
            printf("-------------------- Print list --------------------%n");
            printf("%n%n");

            printf("-------------------- Print listArray --------------------%n");
            printGenerics(ParameterizedClass.class.getField("listArray"));
            printf("-------------------- Print listArray --------------------%n");
            printf("%n%n");

            printf("-------------------- Print wildUnderNumbers --------------------%n");
            printGenerics(ParameterizedClass.class.getField("wildUnderNumbers"));
            printf("-------------------- Print wildUnderNumbers --------------------%n");
            printf("%n%n");

            printf("-------------------- Print wildUpperNumbers --------------------%n");
            printGenerics(ParameterizedClass.class.getField("wildUpperNumbers"));
            printf("-------------------- Print wildUpperNumbers --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m1 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m1", Object.class, Object.class));
            printf("-------------------- Print m1 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m2 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m2"));
            printf("-------------------- Print m2 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m3 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m3"));
            printf("-------------------- Print m3 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m4 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m4", List.class));
            printf("-------------------- Print m4 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print NonGenericClass --------------------%n");
            printGenerics(NonGeneric.class);
            printf("-------------------- Print NonGenericClass --------------------%n");
            printf("%n%n");

            printf("-------------------- Print ActualClass --------------------%n");
            printGenerics(Actual.class);
            printf("-------------------- Print ActualClass --------------------%n");
            printf("%n%n");
        } catch (NoSuchFieldException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private static void printGenerics(Object target) {
        if (target instanceof Class) {
            printf("<<<<< Class#getTypeParameterTypes >>>>>%n");
            TypeVariable[] tvs = ((Class<?>)target).getTypeParameters();
            if (tvs.length > 0) {
                for (TypeVariable tv : tvs) {
                    dispatch(tv);
                }
            } else {
                printf("No Parameter Class[%s]%n", target);
            }
        } else if (target instanceof Field) {
           printf("<<<<< Field#getGenericTypes >>>>>%n");
            dispatch(((Field)target).getGenericType());
        } else if (target instanceof Method) {
            Method m = (Method)target;
            Type[] pts = m.getGenericParameterTypes();
            printf("<<<<< Method#getGenericParameterTypes >>>>>%n");
            for (Type pt : pts) {
                dispatch(pt);
            }

            Type rts = m.getGenericReturnType();
            printf("<<<<< Method#getGenericReturnType >>>>>%n");
            dispatch(rts);

            Type[] ets = m.getGenericExceptionTypes();
            printf("<<<<< Method#getGenericExceptionTypes >>>>>%n");
            for (Type et : ets) {
                dispatch(et);
            }
        } else if (target instanceof Constructor) {
            Constructor c = (Constructor)target;
            Type[] pts = c.getGenericParameterTypes();
            printf("<<<<< Constructor#getGenericParameterTypes >>>>>%n");
            for (Type pt : pts) {
                dispatch(pt);
            }

            Type[] ets = c.getGenericExceptionTypes();
            printf("Constructor#getGenericExceptionTypes >>>>>%n");
            for (Type et : ets) {
                dispatch(et);
            }
        } else {
            printf("Unknown Type... [%s]%n", target);
        }
    }

    private static void dispatch(Type type) {
        if (type instanceof TypeVariable) {
            printTypeVariables((TypeVariable<?>)type);
        } else if (type instanceof ParameterizedType) {
            printParameterizedType((ParameterizedType)type);
        } else if (type instanceof WildcardType) {
            printWildcardType((WildcardType)type);
        } else if (type instanceof GenericArrayType) {
            printGenericArrayType((GenericArrayType)type);
        } else if (type instanceof Class) {
            printf("Class class => %s%n", type);
        } else {
            printf("Unkown Type... => %s%n", type);
        }
    }

    private static void printTypeVariables(TypeVariable<?> tv) {
        printf("TypeVariable#getName => %s%n", tv.getName());
        printf("TypeVariable#getGenericDeclaration => %s%n", tv.getGenericDeclaration());
        for (Type t : tv.getBounds()) {
            printf("TypeVariable#getBounds ---->%n");
            dispatch(t);
        }
    }

    private static void printParameterizedType(ParameterizedType pt) {
        printf("ParameterizedType#getOwnerType => %s%n", pt.getOwnerType());
        printf("ParameterizedType#getRawType => %s%n", pt.getRawType());
        for (Type t : pt.getActualTypeArguments()) {
            printf("ParameterizedType#getActualTypeArguments ---->%n");
            dispatch(t);
        }
    }

    private static void printWildcardType(WildcardType wt) {
        for (Type t : wt.getLowerBounds()) {
            printf("WildcardType#getLowerBounds ---->%n");
            dispatch(t);
        }
        for (Type t : wt.getUpperBounds()) {
            printf("WildcardType#getUpperBounds ---->%n");
            dispatch(t);
        }
    }

    private static void printGenericArrayType(GenericArrayType gat) {
        printf("GenericArrayType#getGenericComponentType ---->%n");
        dispatch(gat.getGenericComponentType());
    }

    private static void printf(String format, Object... args) {
        System.out.printf(format, args);
    }
}

System.out.printfを書きまくるのがもう面倒だったので、printfとショートカットを作りました…。

プログラムの実行結果と、取得している型情報を並べてみます。

class ParameterizedClass<T>
-------------------- Print ParameterizedClass --------------------
<<<<< Class#getTypeParameterTypes >>>>>
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print ParameterizedClass --------------------
    public String nonGeneric;
-------------------- Print nonGeneric --------------------
<<<<< Field#getGenericTypes >>>>>
Class class => class java.lang.String
-------------------- Print nonGeneric --------------------
    public T field;
-------------------- Print field --------------------
<<<<< Field#getGenericTypes >>>>>
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print field --------------------
    public T[] array;
-------------------- Print array --------------------
<<<<< Field#getGenericTypes >>>>>
GenericArrayType#getGenericComponentType ---->
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print array --------------------
    public List<String> strings;
-------------------- Print strings --------------------
<<<<< Field#getGenericTypes >>>>>
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
Class class => class java.lang.String
-------------------- Print strings --------------------
    public List<T> list;
-------------------- Print list --------------------
<<<<< Field#getGenericTypes >>>>>
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print list --------------------
    public List<T>[] listArray;
-------------------- Print listArray --------------------
<<<<< Field#getGenericTypes >>>>>
GenericArrayType#getGenericComponentType ---->
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print listArray --------------------
    public List<? extends Number> wildUnderNumbers;
-------------------- Print wildUnderNumbers --------------------
<<<<< Field#getGenericTypes >>>>>
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
WildcardType#getUpperBounds ---->
Class class => class java.lang.Number
-------------------- Print wildUnderNumbers --------------------
    public List<? super Number> wildUpperNumbers;
-------------------- Print wildUpperNumbers --------------------
<<<<< Field#getGenericTypes >>>>>
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
WildcardType#getLowerBounds ---->
Class class => class java.lang.Number
WildcardType#getUpperBounds ---->
Class class => class java.lang.Object
-------------------- Print wildUpperNumbers --------------------
    public <A, B> void m1(A a, B b) { }
-------------------- Print m1 --------------------
<<<<< Method#getGenericParameterTypes >>>>>
TypeVariable#getName => A
TypeVariable#getGenericDeclaration => public void ParameterizedClass.m1(java.lang.Object,java.lang.Object)
TypeVariable#getBounds ---->
Class class => class java.lang.Object
TypeVariable#getName => B
TypeVariable#getGenericDeclaration => public void ParameterizedClass.m1(java.lang.Object,java.lang.Object)
TypeVariable#getBounds ---->
Class class => class java.lang.Object
<<<<< Method#getGenericReturnType >>>>>
Class class => void
<<<<< Method#getGenericExceptionTypes >>>>>
-------------------- Print m1 --------------------
    public <A extends Number> A m2() { return null; };
-------------------- Print m2 --------------------
<<<<< Method#getGenericParameterTypes >>>>>
<<<<< Method#getGenericReturnType >>>>>
TypeVariable#getName => A
TypeVariable#getGenericDeclaration => public java.lang.Number ParameterizedClass.m2()
TypeVariable#getBounds ---->
Class class => class java.lang.Number
<<<<< Method#getGenericExceptionTypes >>>>>
-------------------- Print m2 --------------------
    public <A extends java.io.IOException, B extends RuntimeException> void m3() throws A, B { }
-------------------- Print m3 --------------------
<<<<< Method#getGenericParameterTypes >>>>>
<<<<< Method#getGenericReturnType >>>>>
Class class => void
<<<<< Method#getGenericExceptionTypes >>>>>
TypeVariable#getName => A
TypeVariable#getGenericDeclaration => public void ParameterizedClass.m3() throws java.io.IOException,java.lang.RuntimeException
TypeVariable#getBounds ---->
Class class => class java.io.IOException
TypeVariable#getName => B
TypeVariable#getGenericDeclaration => public void ParameterizedClass.m3() throws java.io.IOException,java.lang.RuntimeException
TypeVariable#getBounds ---->
Class class => class java.lang.RuntimeException
-------------------- Print m3 --------------------
    public <A> void m4(List<? extends A> list) { }
-------------------- Print m4 --------------------
<<<<< Method#getGenericParameterTypes >>>>>
ParameterizedType#getOwnerType => null
ParameterizedType#getRawType => interface java.util.List
ParameterizedType#getActualTypeArguments ---->
WildcardType#getUpperBounds ---->
TypeVariable#getName => A
TypeVariable#getGenericDeclaration => public void ParameterizedClass.m4(java.util.List)
TypeVariable#getBounds ---->
Class class => class java.lang.Object
<<<<< Method#getGenericReturnType >>>>>
Class class => void
<<<<< Method#getGenericExceptionTypes >>>>>
-------------------- Print m4 --------------------
class NonGeneric extends ParameterizedClass<Object> { }
-------------------- Print NonGenericClass --------------------
<<<<< Class#getTypeParameterTypes >>>>>
No Parameter Class[class NonGeneric]
-------------------- Print NonGenericClass --------------------
class Actual<String> extends ParameterizedClass<String> { }
-------------------- Print ActualClass --------------------
<<<<< Class#getTypeParameterTypes >>>>>
TypeVariable#getName => String
TypeVariable#getGenericDeclaration => class Actual
TypeVariable#getBounds ---->
Class class => class java.lang.Object
-------------------- Print ActualClass --------------------
class ParameterizedClass2<T extends Number> { }
-------------------- Print ParameterizedClass2 --------------------
<<<<< Class#getTypeParameterTypes >>>>>
TypeVariable#getName => T
TypeVariable#getGenericDeclaration => class ParameterizedClass2
TypeVariable#getBounds ---->
Class class => class java.lang.Number
-------------------- Print ParameterizedClass2 --------------------

Classクラスはさておき、その他のTypeインターフェースのサブインターフェースのインスタンスが取得できた場合の挙動について、まとめてみましょう。

TypeVariable

getName

型変数が適用されたものに対する、「字面」が取得できます。宣言によって「T」とか「E」とかのこともありますが、たとえみたいな具体的な適用の仕方をしても、取得できるのは「String」という文字列になります。

getGenericDeclaration

型変数を宣言した、クラスやメソッドの情報が取れます。クラスやフィールドだと、その定義が行われたクラス自体、メソッドはそのメソッド自体ですね。
戻り値はGenericDeclarationインターフェースのサブ型となりますが、Class/Constructor/Methodのいずれかになると思われます。

getBounds

型変数に対する上限を表す、Typeの配列が取得できます。

ParameterizedType

getOwnerType

今回は常にnullでしたね…。Javadocによると、「この型がメンバーである型を表す Type オブジェクトを返します。」とのことですが??

getRawType

型情報を適用するように宣言された、Typeを返却します。例えば、Listであれば、java.util.Listが返ります。

getActualTypeArguments

この型に対する、型引数Typeの配列が返却されます。どのようなTypeが返却されるかは、型引数の適用方法によって異なります。Listのような宣言であればClassクラスが返却され、Listのような宣言であればTypeVariableが返却され、Listのような宣言であればWildcardTypeが返却されます。

WildcardType

getLowerBounds

ワイルドカードの下限を表すTypeの配列を返却します。

getUpperBounds

ワイルドカードの上限を表すTypeの配列を返却します。

GenericArrayType

getGenericComponentType

配列に適用されたTypeの値が返却されます。ParameterizedType#getActualTypeArgumentsのように、どのような適用のされ方をしたかによって、返却されるTypeのサブ型は異なります。

…と、長かったですがこんなところです。ワイルドカードの上限/下限とかならClassクラスが取得できますが、肝心のTypeVariableからはあくまで「字面」しか取れないところが「う〜ん」ってところですね。

ParameterizedTypeだったらよいのですが。

クラスの宣言自体に使われた、

class Actual<String> extends ParameterizedClass<String> { }

の部分はホントに「String」という文字でしか取得できないので、そこに書かれているのが「T」みたいなものなのか、もっと具体的なクラスなのかはリフレクション上はあんまり関係がないみたいですね。

パラメータ化された親クラスやインタフェースを使って、型情報を埋め込んだ場合についてはうまくすれば継承や実装時に適用したClassクラスの取得が可能になるようです。

いやぁ、いろいろ勉強になりました。

最後に、コード全体を載せておきます。
ParameterizedTypeExample.java

import java.lang.reflect.*;

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

public class ParameterizedTypeExample {
    public static void main(String[] args) {
        try {
            printf("-------------------- Print ParameterizedClass --------------------%n");
            printGenerics(ParameterizedClass.class);
            printf("-------------------- Print ParameterizedClass --------------------%n");
            printf("%n%n");

            printf("-------------------- Print nonGeneric --------------------%n");
            printGenerics(ParameterizedClass.class.getField("nonGeneric"));
            printf("-------------------- Print nonGeneric --------------------%n");
            printf("%n%n");

            printf("-------------------- Print field --------------------%n");
            //printf("Field#getType => %s%n",
            //       ParameterizedClass.class.getField("field").getType().getName());
            printGenerics(ParameterizedClass.class.getField("field"));
            printf("-------------------- Print field --------------------%n");
            printf("%n%n");

            printf("-------------------- Print array --------------------%n");
            printGenerics(ParameterizedClass.class.getField("array"));
            printf("-------------------- Print array --------------------%n");
            printf("%n%n");

            printf("-------------------- Print strings --------------------%n");
            printGenerics(ParameterizedClass.class.getField("strings"));
            printf("-------------------- Print strings --------------------%n");
            printf("%n%n");

            printf("-------------------- Print list --------------------%n");
            printGenerics(ParameterizedClass.class.getField("list"));
            printf("-------------------- Print list --------------------%n");
            printf("%n%n");

            printf("-------------------- Print listArray --------------------%n");
            printGenerics(ParameterizedClass.class.getField("listArray"));
            printf("-------------------- Print listArray --------------------%n");
            printf("%n%n");

            printf("-------------------- Print wildUnderNumbers --------------------%n");
            printGenerics(ParameterizedClass.class.getField("wildUnderNumbers"));
            printf("-------------------- Print wildUnderNumbers --------------------%n");
            printf("%n%n");

            printf("-------------------- Print wildUpperNumbers --------------------%n");
            printGenerics(ParameterizedClass.class.getField("wildUpperNumbers"));
            printf("-------------------- Print wildUpperNumbers --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m1 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m1", Object.class, Object.class));
            printf("-------------------- Print m1 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m2 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m2"));
            printf("-------------------- Print m2 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m3 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m3"));
            printf("-------------------- Print m3 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print m4 --------------------%n");
            printGenerics(ParameterizedClass.class.getMethod("m4", List.class));
            printf("-------------------- Print m4 --------------------%n");
            printf("%n%n");

            printf("-------------------- Print NonGenericClass --------------------%n");
            printGenerics(NonGeneric.class);
            printf("-------------------- Print NonGenericClass --------------------%n");
            printf("%n%n");

            printf("-------------------- Print ActualClass --------------------%n");
            printGenerics(Actual.class);
            printf("-------------------- Print ActualClass --------------------%n");
            printf("%n%n");

            printf("-------------------- Print ParameterizedClass2 --------------------%n");
            printGenerics(ParameterizedClass2.class);
            printf("-------------------- Print ParameterizedClass2 --------------------%n");
            printf("%n%n");
        } catch (NoSuchFieldException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private static void printGenerics(Object target) {
        if (target instanceof Class) {
            printf("<<<<< Class#getTypeParameterTypes >>>>>%n");
            TypeVariable[] tvs = ((Class<?>)target).getTypeParameters();
            if (tvs.length > 0) {
                for (TypeVariable tv : tvs) {
                    dispatch(tv);
                }
            } else {
                printf("No Parameter Class[%s]%n", target);
            }
        } else if (target instanceof Field) {
           printf("<<<<< Field#getGenericTypes >>>>>%n");
            dispatch(((Field)target).getGenericType());
        } else if (target instanceof Method) {
            Method m = (Method)target;
            Type[] pts = m.getGenericParameterTypes();
            printf("<<<<< Method#getGenericParameterTypes >>>>>%n");
            for (Type pt : pts) {
                dispatch(pt);
            }

            Type rts = m.getGenericReturnType();
            printf("<<<<< Method#getGenericReturnType >>>>>%n");
            dispatch(rts);

            Type[] ets = m.getGenericExceptionTypes();
            printf("<<<<< Method#getGenericExceptionTypes >>>>>%n");
            for (Type et : ets) {
                dispatch(et);
            }
        } else if (target instanceof Constructor) {
            Constructor c = (Constructor)target;
            Type[] pts = c.getGenericParameterTypes();
            printf("<<<<< Constructor#getGenericParameterTypes >>>>>%n");
            for (Type pt : pts) {
                dispatch(pt);
            }

            Type[] ets = c.getGenericExceptionTypes();
            printf("Constructor#getGenericExceptionTypes >>>>>%n");
            for (Type et : ets) {
                dispatch(et);
            }
        } else {
            printf("Unknown Type... [%s]%n", target);
        }
    }

    private static void dispatch(Type type) {
        if (type instanceof TypeVariable) {
            printTypeVariables((TypeVariable<?>)type);
        } else if (type instanceof ParameterizedType) {
            printParameterizedType((ParameterizedType)type);
        } else if (type instanceof WildcardType) {
            printWildcardType((WildcardType)type);
        } else if (type instanceof GenericArrayType) {
            printGenericArrayType((GenericArrayType)type);
        } else if (type instanceof Class) {
            printf("Class class => %s%n", type);
        } else {
            printf("Unkown Type... => %s%n", type);
        }
    }

    private static void printTypeVariables(TypeVariable<?> tv) {
        printf("TypeVariable#getName => %s%n", tv.getName());
        printf("TypeVariable#getGenericDeclaration => %s%n", tv.getGenericDeclaration());
        for (Type t : tv.getBounds()) {
            printf("TypeVariable#getBounds ---->%n");
            dispatch(t);
        }
    }

    private static void printParameterizedType(ParameterizedType pt) {
        printf("ParameterizedType#getOwnerType => %s%n", pt.getOwnerType());
        printf("ParameterizedType#getRawType => %s%n", pt.getRawType());
        for (Type t : pt.getActualTypeArguments()) {
            printf("ParameterizedType#getActualTypeArguments ---->%n");
            dispatch(t);
        }
    }

    private static void printWildcardType(WildcardType wt) {
        for (Type t : wt.getLowerBounds()) {
            printf("WildcardType#getLowerBounds ---->%n");
            dispatch(t);
        }
        for (Type t : wt.getUpperBounds()) {
            printf("WildcardType#getUpperBounds ---->%n");
            dispatch(t);
        }
    }

    private static void printGenericArrayType(GenericArrayType gat) {
        printf("GenericArrayType#getGenericComponentType ---->%n");
        dispatch(gat.getGenericComponentType());
    }

    private static void printf(String format, Object... args) {
        System.out.printf(format, args);
    }
}

class ParameterizedClass<T> {
    public String nonGeneric;
    public T field;
    public T[] array;
    public List<String> strings;
    public List<T> list;
    public List<T>[] listArray;
    public List<? extends Number> wildUnderNumbers;
    public List<? super Number> wildUpperNumbers;

    public <A, B> void m1(A a, B b) { }
    public <A extends Number> A m2() { return null; };
    public <A extends java.io.IOException, B extends RuntimeException> void m3() throws A, B { }
    public <A> void m4(List<? extends A> list) { }
}

class NonGeneric extends ParameterizedClass<Object> { }
class Actual<String> extends ParameterizedClass<String> { }
class ParameterizedClass2<T extends Number> { }