web-dev-qa-db-ja.com

Java 8 Streams:Collectors.toMapがワイルドカードを使用したジェネリックに対して異なる動作をするのはなぜですか?

Listの数字があると仮定します。 Listの値は、タイプIntegerDoubleなどにすることができます。このようなListを宣言する場合、ワイルドカード(?)を使用して、または使用せずに宣言できますワイルドカード。

final List<Number> numberList = Arrays.asList(1, 2, 3D);
final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D);

それで、streamListcollectを介してMapにしたいのは、Collectors.toMapを使用して(明らかに、以下のコードは問題を説明するための例にすぎません) 。 numberListをストリーミングすることから始めましょう:

final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);

numberList.stream().collect(Collectors.toMap(
        // Here I can invoke "number.intValue()" - the object ("number") is treated as a Number
        number -> Integer.valueOf(number.intValue()),
        number -> number));

しかし、私はwildcardListで同じ操作を行うことはできません:

final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.toMap(
        // Why is "number" treated as an Object and not a Number?
        number -> Integer.valueOf(number.intValue()),
        number -> number));

コンパイラは、number.intValue()の呼び出しに対して次のメッセージを表示して不平を言っています。

Test.Java:シンボルが見つかりません
symbol:メソッドintValue()
location:タイプJava.lang.Objectの可変数

コンパイラエラーから、ラムダのnumberObjectとしてではなくNumberとして扱われていることは明らかです。

だから、今私の質問に:

  • ワイルドカードバージョンのListを収集する場合、ワイルドカード以外のバージョンのListのように機能しないのはなぜですか?
  • ラムダのnumber変数がObjectではなくNumberであると見なされるのはなぜですか?
27
wassgren

それを正しくしないのは型推論です。 type引数を明示的に指定すると、期待どおりに機能します。

List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
wildCardList.stream().collect(Collectors.<Number, Integer, Number>toMap(
                                  number -> Integer.valueOf(number.intValue()),
                                  number -> number));

これは既知のjavacバグです。 推論はキャプチャ変数を上限にマップすべきではありません 。マウリツィオシマダモアによると、ステータスは、

8で問題が発生したため修正が試みられてバックアウトされたため、9ですべてを実行しながら8でより保守的な修正を行った

どうやら、修正プログラムはまだプッシュされていません。 ( JoelBorggrén-Franck に感謝します。私を正しい方向に向けてくれました。)

35
aioobe

List<? extends Number> wildcardList形式の宣言は、「NumberまたはNumberのサブクラスである不明なタイプのリスト」を意味します。興味深いことに、未知のタイプが名前で参照されている場合、未知のタイプを持つ同じ種類のリストが機能します。

static <N extends Number> void doTheThingWithoutWildCards(List<N> numberList) {
    numberList.stream().collect(Collectors.toMap(
      // Here I can invoke "number.intValue()" - the object is treated as a Number
      number -> number.intValue(),
      number -> number));
}

ここでは、Nはまだ「Numberである不明な型またはNumberのサブクラス」ですが、List<N>を意図したとおりに処理できます。不明なタイプList<? extends Number>に互換性があるという制約として、List<N>extends Numberに問題なく割り当てることができます。

final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
doTheThingWithoutWildCards(wildCardList); // or:
doTheThingWithoutWildCards(Arrays.asList(1, 2, 3D));

型推論に関する章 は読みにくいです。この点でワイルドカードと他のタイプに違いがあるかどうかはわかりませんが、あるべきだとは思いません。っていうことは どちらか コンパイラのバグ または仕様による制限ですが、論理的には、 ワイルドカードが機能しない理由はありません。

3
Holger