Java 8 Streams:为什么 Collectors.toMap 对于带有通配符的泛型会有不同的表现?
Java 8 Streams: why does Collectors.toMap behave differently for generics with wildcards?
假设您有 List
个数字。 List
中的值可以是 Integer
、Double
等类型。当您声明这样的 List
时,可以使用通配符 (?
) 或没有通配符。
final List<Number> numberList = Arrays.asList(1, 2, 3D);
final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D);
所以,现在我想 stream
在 List
和 collect
上使用 Collectors.toMap
全部变成 Map
(显然是下面的代码只是一个例子来说明问题)。让我们从流式传输 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: cannot find symbol
symbol: method intValue()
location: variable number of type java.lang.Object
从编译器错误可以明显看出,lambda 中的 number
被视为 Object
而不是 Number
。
所以,现在回答我的问题:
- 在收集
List
的通配符版本时,为什么它不像 List
的非通配符版本那样工作?
- 为什么 lambda 中的
number
变量被认为是 Object
而不是 Number
?
是类型推断不对。如果您明确提供类型参数,它会按预期工作:
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 错误:Inference should not map capture variables to their upper bounds。根据 Maurizio Cimadamore 的说法,
a fix was attempted then backed out as it was breaking cases in 8, so we went for a more conservative fix in 8 while doing the full thing in 9
显然还没有推送修复程序。 (感谢
Joel Borggrén-Franck 为我指明了正确的方向。)
这是由于 type inference,在第一种情况下你声明了 List<Number>
所以当你写 number -> Integer.valueOf(number.intValue())
时编译器没有任何反对,因为变量 number
的类型是 java.lang.Number
但是在第二种情况下你声明了 final List<? extends Number> wildCardList
因为
Collectors.toMap
被翻译成类似 Collectors.<Object, ?, Map<Object, Number>toMap
的形式,例如
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Object, ?, Map<Object, Object>> collector = Collectors.toMap(
// Why is number treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number);
wildCardList.stream().collect(collector);
结果在表达式中
number -> Integer.valueOf(number.intValue()
变量 number
的类型是 Object 并且 class Object 中没有定义方法 intValue()
。因此你会得到编译错误。
您需要传递收集器类型参数,这有助于编译器解决 intValue()
错误,例如
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Number, ?, Map<Integer, Number>> collector = Collectors.<Number, Integer, Number>toMap(
// Why is number treated as an Object and not a Number?
Number::intValue,
number -> number);
wildCardList.stream().collect(collector);
此外,您可以使用方法参考 Number::intValue
而不是 number -> Integer.valueOf(number.intValue())
有关Java 8 中类型推断的更多详细信息,请参阅here。
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));
chapter about Type Inference is not an easy read. I don’t know if there is a difference between wildcards and other types in this regard, but I don’t think that there should be. So its either a compiler bug 或规范的限制,但从逻辑上讲, 没有理由不使用通配符。
你可以这样做:
final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);
numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));
假设您有 List
个数字。 List
中的值可以是 Integer
、Double
等类型。当您声明这样的 List
时,可以使用通配符 (?
) 或没有通配符。
final List<Number> numberList = Arrays.asList(1, 2, 3D);
final List<? extends Number> wildcardList = Arrays.asList(1, 2, 3D);
所以,现在我想 stream
在 List
和 collect
上使用 Collectors.toMap
全部变成 Map
(显然是下面的代码只是一个例子来说明问题)。让我们从流式传输 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: cannot find symbol
symbol: method intValue()
location: variable number of type java.lang.Object
从编译器错误可以明显看出,lambda 中的 number
被视为 Object
而不是 Number
。
所以,现在回答我的问题:
- 在收集
List
的通配符版本时,为什么它不像List
的非通配符版本那样工作? - 为什么 lambda 中的
number
变量被认为是Object
而不是Number
?
是类型推断不对。如果您明确提供类型参数,它会按预期工作:
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 错误:Inference should not map capture variables to their upper bounds。根据 Maurizio Cimadamore 的说法,
a fix was attempted then backed out as it was breaking cases in 8, so we went for a more conservative fix in 8 while doing the full thing in 9
显然还没有推送修复程序。 (感谢 Joel Borggrén-Franck 为我指明了正确的方向。)
这是由于 type inference,在第一种情况下你声明了 List<Number>
所以当你写 number -> Integer.valueOf(number.intValue())
时编译器没有任何反对,因为变量 number
的类型是 java.lang.Number
但是在第二种情况下你声明了 final List<? extends Number> wildCardList
因为
Collectors.toMap
被翻译成类似 Collectors.<Object, ?, Map<Object, Number>toMap
的形式,例如
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Object, ?, Map<Object, Object>> collector = Collectors.toMap(
// Why is number treated as an Object and not a Number?
number -> Integer.valueOf(number.intValue()),
number -> number);
wildCardList.stream().collect(collector);
结果在表达式中
number -> Integer.valueOf(number.intValue()
变量 number
的类型是 Object 并且 class Object 中没有定义方法 intValue()
。因此你会得到编译错误。
您需要传递收集器类型参数,这有助于编译器解决 intValue()
错误,例如
final List<? extends Number> wildCardList = Arrays.asList(1, 2, 3D);
Collector<Number, ?, Map<Integer, Number>> collector = Collectors.<Number, Integer, Number>toMap(
// Why is number treated as an Object and not a Number?
Number::intValue,
number -> number);
wildCardList.stream().collect(collector);
此外,您可以使用方法参考 Number::intValue
而不是 number -> Integer.valueOf(number.intValue())
有关Java 8 中类型推断的更多详细信息,请参阅here。
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));
chapter about Type Inference is not an easy read. I don’t know if there is a difference between wildcards and other types in this regard, but I don’t think that there should be. So its either a compiler bug 或规范的限制,但从逻辑上讲, 没有理由不使用通配符。
你可以这样做:
final List<Number> numberList = Arrays.asList(1, 2, 3D, 4D);
numberList.stream().collect(Collectors.toMap(Number::intValue, Function.identity()));