【java】型パラメータの型情報を実行時に知るには

#0.先に結論
- 型パラメータT付きでクラス継承する
 - コンストラクタにT型可変長引数を持たせることでTの実際の型が取得できる

<!-- 
- Objectでパラメータ化して継承したクラスを自作する手もあるが冗長。　
 - 型パラメータはClass型引数で代用する　 
 - 型の監視は別スレッドで。
-->

---

#1.課題
配列の要素の型は `array.getClass().getComponentType()` で後から分かるけど、
ArrayList、LinkedList等のListで同じことをやるのは難しい！

---
#2.対象読者

- javaで `Class` クラスを利用したことのある人
- 型パラメータに興味のある人

---

#3.ダメな例

---

準備

```java
class Hoge
{
    public static void main(String[] args)
    {
        ArrayList<Number> inteList = new ArrayList<>();
        System.out.println(new ParamGetter(inteList).get());
        //ParamGetterクラスのget()メソッドで
        //「Number」などと表示させたい
    }
}
```

---
##3.1 T.classはできない
```java
class ParamGetter
{
    Class paramClass;
    <T> ParamGetter(List<T> list)
    {
        this.paramClass = T.class;
        //×これはNG
    }
    Class get(){return paramClass;}
}
```

---
##3.2 リフレクションしても失敗[1]
```java
//(リフレクションしたことある人向け)
class ParamGetter
{
    Type paramType;
    <T> ParamGetter(List<T> list)
    {
        this.paramType = ((ParameterizedType)new ArrayList<T>(){}.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
    Type get(){return paramType;}
}
```
結果:「T」と表示されてしまう
`Field` クラスの `getGenericType()` メソッドを使う方法[2]でも同じ。

---
##3.3 `toArray()` で配列化しても失敗
```java
class ParamGetter
{
    Object array;
    <T>ParamGetter(List<T> list)
    {
        this.array = (T[])list.toArray();
        //☆System.out.println((T[])list.toArray());
    }

    Type get() throws NoSuchFieldException, SecurityException
    {
        return this.array.getClass().getComponentType();
    }
}

```
結果(main):「class java.lang.Object」と表示されてしまう。

<!--
-
蛇足
「Listから要素を一つ取ってその型を調べる」というアイデアもあろうが、実際は不適切。

- `List<Number>` から `Integer[]` はできても `Number[]` はできない
- 要素数0の場合もある
-->
---

#4. 根本的な原因

メソッドに渡す段階で型情報が消えている

---

3.3はまず`List<T>`型仮引数に`ArrayList<Number>`型実引数を渡している。 
次に`T`型配列に変換して `Object` 型のフィールドに格納している。 
そして、`get()` が呼ばれると、「実行時の型」(配列型)を求め、その配列の要素の型を調べる。

---

その結果が `Object` と出たのだから、
`T` = `Number` ではなく `T` = `Object` となってしまったことになる。

---

事実、☆のコードを実行すると、`Object[]` 型である旨が表示される。
→やはり `T[]` = `Object[]`

```java
System.out.println((T[])list.toArray());//Object[]型と出る。
```
---

ここから「型パラメータの情報が、引数として渡す間で消えてしまった」ということがわかる。
実際にはコンパイル時に消される(受け取り可能な最も抽象的な型になる)[3]

---
#5. 解決策
実行時に消えるものは例えリフレクションしても取れない(リフレクションは実行時の技術[4])
→

- 呼び出すとき、消えないところに書く
- クラス自体に型パラメータを持たせ、コンストラクタでそれを利用

---
##5.1 型パラメータを引数に置換え

class Hoge&lt;T&gt;|Fuga生存|Fuga as
:-:|:-:|:-:
new Hoge();|×|インスタンスメソッドが持つ型パラメータ
new Hoge&lt;Fuga&gt;();|〇|クラスが持つ型パラメータ[5]
new Hoge(Fuga.class)|△|Class型引数
△･･･不可能ではないが、冗長な使用になってしまう

<!--

---

##5.2 任意のraw型に対応する1つのクラスを作れるか
難しい(ルールを決めればできるかも)
`new A<B<C>>()`→`new Hoge("A","B","C")`
`new A<B>(C)` → `new Hoge("A","B","C")`
曖昧さ回避が課題
またAに対応するクラスとAに継承関係がほしい

-->

---
##5.2 `ArrayList<T>` <br>→ `ArrayList_<T>`

やりたいこと: 
`new ArrayList_<Number>(Object...);` が 
`new ArrayList<Number>(Object...);` と
ほぼ同じ意味を持つようにし、
かつ `Number` を実行時に取得可能にする

---
ほぼ同じ意味を持つ→継承で実現可能
本記事のsaka1029氏のコメントを参考に書く

```java
public class ArrayList_<T> extends ArrayList<T> {

    public final Class<?> clazz;

    public ArrayList_(T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
                "dummy引数を指定してはいけません");
        clazz = dummy.getClass().getComponentType();
    }

    public Class getComponentClass() {
        return clazz;
    }
}
```


---

<!--

##5.3 `ArrayList<T>` <br>→ `ArrayList_(Class t)`(非推奨)
**注:冗長なので非推奨**
やりたいこと: 
`new ArrayList_("Number",Object...);` が 
`new ArrayList<Number>(Object...);` と
ほぼ同じ意味を持つようにし、
かつ `Number` を実行時に取得可能にする

---
ほぼ同じ意味を持つ→継承で実現可能
`Number`の取得→Class型フィールド

```java
class ArrayList_ extends ArrayList<Object>//継承
{
	private Class<?> k;//クラス型フィールド
	//コンストラクタ群: ArrayListのコンストラクタに、それぞれClass型引数を加えたもの
	ArrayList_(Class<?> componentClass){super(); k = componentClass;}
	ArrayList_(Class<?> componentClass, Collection<?> c){super(c);k = componentClass;}
	ArrayList_(Class<?> componentClass, int initialCapacity){super(initialCapacity);k = componentClass;}
    //型情報取得メソッド
	public Class<?> getComponentClass(){return k;}
}
```

---
使い方

```java
	public static void main(String[] args)
	{
		ArrayList_ list = new ArrayList_(Number.class);
		list.add(0);
		list.add(3.6);
		System.out.println(list.get(1));//3.6
		System.out.println(list.getComponentClass());//class java.lang.Number
	}
```




---
##5.3 `ArrayList_`の問題点
`ArrayList<Object>`を継承しているので、このままでは`ArrayList_(Number.class)`としても
`Number`のインスタンスでないものが要素となり得る。

---

要は

```java
ArrayList_ list = new ArrayList_(Number.class);
list.add("I'm not in Number class.");
```
としても動いてしまう。

---

##5.5 問題点の解決
別スレッドで数秒おきに型監視すればよい。
(スライドモードでご覧の方は今だけテキストの方を読んでください。
お手煩わせして申し訳ございません・・・)



```java
class ArrayList_ extends ArrayList<Object>
{
    private Class<?> k;
    ArrayList_(Class<?> componentClass){super();k = componentClass;}
    ArrayList_(Class<?> componentClass, Collection<?> c){super(c);k = componentClass;}
    ArrayList_(Class<?> componentClass, int initialCapacity){super(initialCapacity);k = componentClass;}
    public Class<?> getComponentClass(){return k;}

    private byte[] state = {0,10}; //型監視制御用パラメータ
    Runnable monitoring = ()->//型監視を行うプログラム
    {
        while(state[0] != 2)//state[0] 0:監視ON,1:一時停止,2:監視用スレッドの終了
        {
            if(state[0] == 0)
            {//監視ON
                for(int i=0; i < this.size(); i++)//iが不要なら拡張for文でもOK
                    if(!(is_a_k(this.get(i).getClass())))//各要素が継承関係にあるか調べる
                        throw new RuntimeException
                        (
                            "違反する型が検知されました。\n"
                            +i+"番目の要素"+this.get(i)+"の実行時クラス"
                            +this.get(i).getClass().getName()
                            +"は"+k.getName()+"またはそのサブクラスとみることができません。"
                        );
            }
            //監視ON, 一時停止
            try{Thread.sleep(state[1]*1000);}catch(InterruptedException e1){e1.printStackTrace();}
            //state[1]: 監視周期[秒]                            
        }
        //監視スレッド終了
        return;
    };
    public void resumeMonitor(){stateRewriter(0,0);}//監視再開
    public void pauseMonitor(){stateRewriter(0,1);}//監視の一時停止
    public void killMonitor(){stateRewriter(0,2);}//監視終了
    public void setMonitoringPace(byte second){stateRewriter(1,second);}//監視周期[秒]変更
    private void stateRewriter(int index, int b)
    {
        if(state[0]!=2)
            state[index]=(byte)b;
        else throw new RuntimeException("型監視スレッドは終了しています");
    }
    private boolean is_a_k(Class<?> each_clazz)
    //引数の意味するクラスがkのクラス(リストの要素のクラス)の一種か調べる
    {
        Class<?> superiorClass = each_clazz;
        while(superiorClass != k)//引数のクラスの親をたどり、kに一致しうるか調査
        {
            superiorClass = superiorClass.getSuperclass();
            if(superiorClass == Object.class)
                return false;
        }
        return true;
    }
    {new Thread(monitoring).start();} //イニシャライザで型監視スレッド起動
}
```


---
型監視に引っかかる例

```java
	public static void main(String[] args)
	{
		ArrayList_ list = new ArrayList_(Number.class);
		list.add(0);
		list.add("a");
	}
```

---
結果

```
Exception in thread "Thread-0" java.lang.RuntimeException: 違反する型が検知されました。
1番目の要素aの実行時クラスjava.lang.Stringはjava.lang.Numberまたはそのサブクラスとみることができません。
	at superCommon.ArrayList_.lambda$0(Main1.java:801)
	at java.base/java.lang.Thread.run(Thread.java:834)
```

-->

---
#6 (おまけ) `new Hoge<t.getClass()>`的なことを可能にする
ジェネリクス型`Hoge<T>`のインスタンスは`Class<T>`型の変数からは作れない。

---

要は `new Hoge<t.getClass()>` はできない。
メソッド内に書いておくなら `new Hoge<?>` としてある程度可能だけど、
`Hoge<t.getClass()>.getClass().getDeclaredConstructor().newInstance()`
として任意の場所でジェネリクス型インスタンスを作ることはできない。

---
これをしたい場合は、引数に`Class<?>`型変数を渡せばよい。

```java
public class ArrayList_<T> extends ArrayList<T> {

    public final Class<?> clazz;

    public ArrayList_(T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
                "dummy引数を指定してはいけません");
        clazz = dummy.getClass().getComponentType();
    }

    public ArrayList_(Class<T> clazz)//これを追加
    {
        this.clazz = clazz;
    }

    public Class getComponentClass() {
        return clazz;
    }
}
```

---

# 参考
[1]http://unageanu.hatenablog.com/entry/20071105/1194264963
[2]https://codeday.me/jp/qa/20181216/16785.html
[3]https://qiita.com/pebblip/items/1206f866980f2ff91e77
本文中の「受け取り可能な最も抽象的な型」は[3]の「上限境界」を意味する。
[4]https://java.keicode.com/lang/reflection.php
[5]本記事にお寄せいただいた、saka1029氏のご指摘
その他:https://java-beginner.com/thread-thread-and-runnable/