最近、Effective Java第二版を読んでいるのですが(英語の訓練も兼ねて洋書を)、
面白い構文があったのでご紹介します。
- 作者: Joshua Bloch
- 出版社/メーカー: Prentice Hall
- 発売日: 2008/05/08
- メディア: ペーパーバック
- 購入: 6人 クリック: 65回
- この商品を含むブログ (42件) を見る
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);