Javaの可変長引数とジェネリックスの裏技的な何か。

最近はJavaしか触ってないので。


参考

Javaの型推論Utilsクラス - yukobaのブログ (型推論とは言い切れない気がするけど。)
Java変態文法最速マスター - プログラマーの脳みそ 4. 配列の辺り

package my.sample;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class GenericsTest {
	static class Hoge {
		public int x = 100;
	}
	
	static class Fuga {
		public int y = 200;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(T... dummy) throws InstantiationException, IllegalAccessException {
		return (T) dummy.getClass().getComponentType().newInstance();
	}
	
	@Test
	public void genericsSampleTest() throws InstantiationException, IllegalAccessException {
		String x = newInstance();
		assertEquals(new String(), x);
		
		Hoge hoge = newInstance();
		assertEquals(100, hoge.x);
		
		Fuga fuga = newInstance();
		assertEquals(200, fuga.y);
		
	}
}

可変長引数は引数の指定がない場合、要素が0個の配列として定義される。
可変長引数の型情報としてジェネリック型を指定すれば、コンパイル時にうまくその配列の情報を渡せるっぽいので、
getComponentTypeで配列が持つ型の情報を取得することで、ちょっと型推論っぽい挙動ができるよ、という裏技。


ジェネリック型はnew T();とか、T.classとかすることはできない。Javaジェネリック型はC++のテンプレートほど凄くて無駄なものじゃなくて、基底クラスがObjectであることを利用したシンタックスシュガー的なもの。要するに内部では全部Objectに置き換えており、使う場合に適宜キャストして辻褄を合わせてるような感じ。
・・・という、認識だったのだけど、その考え方なら T ... だって実際は Object ... になるんじゃないの?ってなるわけで。
配列なんてタダのコンテナなんだから、Stringの配列で与えても、Objectの配列で与えても、
new String{"aaa", "bbb"};
new Object
{"aaa", "bbb"};
と、どちらも定義できるので、ジェネリック型の場合は、下のほうで変換されるもんだと思ってた。
コンパイル時に可変長引数として与えられた値が全てStringであることを保証できれば、実行時に何にも問題ないと思っていたのだけど。
こんな感じでまだ理解できてない。(間違えた認識だろうから取り消し線引いておきました)


ORMとかによくある Hoge hoge = mapping(Hoge.class); とかいちいちマッピング先のクラスの情報を与える必要がなくなるのだけど、
可変長引数なので、何かしら渡すことができてしまうし、それが影響を与える。
new String()とか渡せば、可変長引数が String ... dummy となってしまうわけで、
String.class.newInstance()を行ってしまい、うまく行かないときが出てくる。

	Hoge hoge = newInstance(new String()); // コンパイルエラー


当然だけど、可変長引数を意識させないように隠そうとしてもダメ。
Objectのインスタンスが生成されてしまうので、実行時エラーになる。
マクロみたいに愚直に展開してくれる機能があればこういう隠蔽ぐらいはできるんだけどなぁ。

	@SafeVarargs
	@SuppressWarnings("unchecked")
	private static <T> T newInstance_(T... dummy) throws InstantiationException, IllegalAccessException {
		return (T) dummy.getClass().getComponentType().newInstance();
	}

	public static <T> T newInstance() throws InstantiationException, IllegalAccessException {
		T var = newInstance_();
		return var;
	}

	// ...

	String x = newInstance(); // 実行時にエラー。ClassCastException


結局、代入先の情報を明示しないといけないわけだし、この可変長引数を使った場合の弊害も出てくる。
知らない人が使ったときに補完でdummyとか出てきたら困惑すると思う。
冗長だけど慣例になりつつある(と思われる)Classを引数に取るようにするのがやはりベターなのだろうなぁ。

	@SuppressWarnings("unchecked")
	public static <T> T newInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException {
		return (T) clazz.newInstance();
	}