読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

Genericsなメソッド呼び出し時の型パラメータ強制指定構文

最近、Effective Java第二版を読んでいるのですが(英語の訓練も兼ねて洋書を)
面白い構文があったのでご紹介します。

Effective Java (Java Series)

Effective Java (Java Series)

Genericsの功罪

一般に、ライブラリまたはフレークワークのAPIは、できるだけ多くのパターンに適合することが求められます。
Java5から導入されたGenericsは、汎用的なAPIを記述する大きな手助けとなります。


Genericsが導入されたことによって、API設計者にとって便利になったことは事実です。
しかしGenericsによる表現の柔軟性を獲得した反面、それがトラブルになることが増えたのも事実です。例えば次のコードを見て下さい。

import java.util.*;

public class Test {
	public static void main(String... args) throws Exception {
		new Test().process();
	}
	
	public void process() {
		final Collection<Integer> integers = Arrays.asList(1, 2, 3);
		final Collection<String> strings = Arrays.asList("a", "b", "c");
		
		final Collection<Object> objects = this.union(integers, strings);

		System.out.println(objects);
	}

	private <E> Collection<E> union(Collection<? extends E> set0, Collection<? extends E> set1) {
		final Set<E> result = new HashSet<E>();
		
		result.addAll(set0);
		result.addAll(set1);
		
		return result;
	}
}


このコードはコンパイルできません。次のようなエラーが出ます。

Test.java:8: 互換性のない型
検出値  : java.util.Collection<java.lang.Object&java.io.Serializable&java.lang.Comparable<? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<?>>>
期待値  : java.util.Collection<java.lang.Object>
                final Collection<Object> objects = union(integers, strings);
                                                        ^
エラー 1 個


このコードがコンパイルエラーになる原因は、union(Collection, Collection)に渡している引数の型パラメータが異なるからです。
どちらもCollection型の値を渡していることは一緒ですが、片方ではIntegerの、もう一方ではStringの型パラメータが付与されています。

こうしてもダメ

かといって、Genericsの型パラメータは共役型ではないので、このように書くこともできません。

import java.util.*;

public class Test {
	public static void main(String... args) throws Exception {
		new Test().process();
	}
	
	public void process() {
		final Collection<Object> integers = Arrays.asList(1, 2, 3);
		final Collection<Object> strings = Arrays.asList("a", "b", "c");
		
		final Collection<Object> objects = this.union(integers, strings);

		System.out.println(objects);
	}

	private <E> Collection<E> union(Collection<? extends E> set0, Collection<? extends E> set1) {
		final Set<E> result = new HashSet<E>();
		
		result.addAll(set0);
		result.addAll(set1);
		
		return result;
	}
}

やはりコンパイルエラーが出ます。

Test.java:5: 互換性のない型
検出値  : java.util.List<java.lang.Integer>
期待値  : java.util.Collection<java.lang.Object>
                final Collection<Object> integers = Arrays.asList(1, 2, 3);
                                                                 ^
Test.java:6: 互換性のない型
検出値  : java.util.List<java.lang.String>
期待値  : java.util.Collection<java.lang.Object>
                final Collection<Object> strings = Arrays.asList("a", "b", "c");
                                                                ^
エラー 2 個

正解

このようなケースでは次のように記述すればOKです。

    this.<Object>union(integers, strings);

これで、型パラメータを強制的にObjectと指定できます。


[識別子].<型パラメータ>[メソッド名] という構文があるんですね。へー。

インナークラスのインスタンス化、すなわち[識別子].new [クラス名] と同じくらい変な構文だなぁ…

import java.util.*;

public class Test {
	public static void main(String... args) throws Exception {
		new Test().process();
	}
	
	public void process() {
		final Collection<Integer> integers = Arrays.asList(1, 2, 3);
		final Collection<String> strings = Arrays.asList("a", "b", "c");
		
		final Collection<Object> objects = this.<Object>union(integers, strings);

		System.out.println(objects);
	}

	private <E> Collection<E> union(Collection<? extends E> set0, Collection<? extends E> set1) {
		final Set<E> result = new HashSet<E>();
		
		result.addAll(set0);
		result.addAll(set1);
		
		return result;
	}
}


ちなみにR42FWの開発において、今のところは一度だけこれを使いました。



(追記)

この記事では、長ったらしい例を取り上げてしまいましたが、とても簡単な例が見つかりました。

import java.util.*;

public class Test {
    public static void main(String... args) throws Exception {

        //実際のところはargsがnullになることはないけどサンプルなのでご勘弁
        final List<String> listOfArgs = (args == null) ? 
            Collections.emptyList() : Arrays.asList(args);
	
        System.out.println(listOfArgs);
    }
}


これはコンパイルエラーになります。

Test.java:5: 互換性のない型
検出値  : java.util.List<capture#49 of ? extends java.lang.Object>
期待値  : java.util.List<java.lang.String>
                final List<String> listOfArgs = (args == null) ?
                                                               ^
エラー 1 個


このように修正すればOKです。

final Set<String> listOfArgs = (args == null) ? 
    Collections.<String>emptyList() : Arrays.asList(args);