総称型をリフレクション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
参考
- Angelika Langer - Java Generics FAQs - Generic And Parameterized Types
- Java Language Specification, Chapter 4. Types, Values, and Variables
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()
では取得できない。
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 で定義されているが 実際は
Class
。instanceof Class
すると true になる。Class にならない場合はどのような時だろうか?
- 返り値は Type で定義されているが 実際は
-
getOwnerType()
で内部クラスの場合の親クラス(Type
クラス)が取得できる。
GenericArrayType
T[]
など総称型の配列を表す場合。 ちなみに List<String>[]
のような配列は作れません。
-
getGenericComponentType()
で中身のType
クラスが取得できます- 例えば
T[]
と宣言された型に対してこれを取得するとT
のType
クラスが返ります
- 例えば
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 | - | - |
チェックに使ったコード
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
というのが導入されていますが見なかったことにする