[Generics]はしっかり設定して[RawType]を防ごう(大事なことなので2回ry)
以下のプログラムはコンパイルエラーになる。
注: Java8にて確認。
例: Hoge.java
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class Hoge {
public static void main(String[] args) {
Map map = new HashMap();
Stream.of(map)
.map(Map::entrySet)
.flatMap(Set::stream)
.forEach(e -> {
System.out.println("key = " + e.getKey() + ", value = " + e.getValue());
});
}
}
なぜコンパイルエラーになるかといえば、タイトルの通り[java.util.Map]にGenericsが設定されていないから。
[java.util.Map.entrySet()]は[java.util.Set<java.util.Map.Entry<K, V>>]を返す様になっていて、この[<K, V>]は[java.util.Map]の設定を引き継ぐ。
ただしこのケースではGenericsの[<K, V>]が設定されていないため[java.util.Map
]が[RawType]として扱われ、[java.util.Map.entrySet
()]は[Generics無し(RawType)]の[java.util.Set]を返却する。
そして[java.util.Set::stream
]もまた、同じく[Generics無し(RawType)]の[java.util.stream.Stream]を返却する。
[RawType]の場合は格納される値は全て[java.lang.Object]として扱われるので、[java.util.stream.Stream]は[java.lang.Object]を扱うと認識されていることになる。
最後の[java.util.stream.Stream.forEach(java.util.function.Consumer)]においても[java.util.function.Consumer.accept(T t)]のGenericsの[T]は[java.lang.Object]として認識されている。
つまり[java.lang.Object]には[getKey()]や[getValue()]なんてメソッドは存在しないので、コンパイルエラーになるというワケ。
似た様なケースは多々発生するので、[RawType]にならない様にしっかりGenericsは設定しようねということになるのだけれど、場合によってはどうしても防げないケースもある。
それは自分の管理していないモジュール、例えばParserなどで[java.lang.Class]を引数に渡して内部でキャストする様なケース。
そういう場合には、仕方がないので自前でキャストしてしまうのが一番手っ取り早い。
ちなみに先ほどのコード(Hoge.java)で無理やりコンパイルエラーを消すためにキャストを仕込むと以下のコード(Fuga.java)になる。
Fuga.java
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
public class Fuga {
public static void main(String[] args) {
Map map = new HashMap();
Stream.of(map)
.map(map -> (Map<Object, Object>) map)
.map(Map::entrySet)
.flatMap(Set::stream)
.forEach(e -> {
System.out.println("key = " + e.getKey() + ", value = " + e.getValue());
});
}
}
これでコンパイルエラーは出なくなる。
ちなみにメソッド参照で[java.util.Map.class::cast]を使いたくなるが、これだとやはりGenericsは欠落してしまうので我慢。