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

総称型をリフレクション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 ](http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html) 
* [Java Language Specification, Chapter 4. Types, Values, and Variables](http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-TypeVariable)

## 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](https://docs.oracle.com/javase/jp/8/api/java/lang/reflect/Type.html)

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

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

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

```java: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 で定義されているが 実際は `Class`。`instanceof Class` すると true になる。Class にならない場合はどのような時だろうか？
* `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<D> の APIドキュメント](https://docs.oracle.com/javase/jp/8/api/java/lang/reflect/TypeVariable.html)の日本語訳はちょっと何言ってるかわからない。

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

 `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  | - | - |


### チェックに使ったコード

```java: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` というのが導入されていますが見なかったことにする