为什么在调用collect()之后使用stream()会得到Stream<Object>?

5
考虑以下未编译的示例:

例如:

List<Integer> list = Arrays.asList(1, 2, -3, 8);

        list.stream()
                .filter(x -> x > 0)
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
                .stream() // Stream<Object>
                .map(x -> x * 2)
                .forEach(System.out::println);

如果我替换

.collect(ArrayList::new, ArrayList::add, ArrayList::addAll)

使用

.collect(Collectors.toList())

代码将被编译。 那么问题是,我该如何使用供应商和累加器(我需要它)编写collect(),以便在之后调用stream()

4
这是因为使用了原始类型 ArrayList。请使用 ArrayList<Integer>::new - ernest_k
就像@ernest_k所说。既然你没有指定ArrayList的泛型类型,编译器将假定它是一个Object,因为Java中的所有东西都是一个Object。 - ygorazevedo
如果你启用了所有的编译器警告,你就会被通知到这样的事情,而不是被它们惊到。 - VGR
1
rgettman已经说过了——这是因为编译器无法推断参数类型。ArrayList<Integer>::new并不总是必需的,例如,List<Integer> anotherList = list.stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll)也可以正常工作。 - MC Emperor
1个回答

9

看起来你使用了 ArrayList::new 的原始方法引用方式来引用 ArrayList 构造函数。

没有推断出类型参数:

.collect(ArrayList::new, ArrayList::add, ArrayList::addAll)
collect方法的三个参数重载需要传入三个参数,第一个参数是一个Supplier<R>。此时R参数类型与这里的T(即Integer)参数类型没有任何关系。唯一能够推断它们之间关系的方式是通过第二个参数,一个BiConsumer<R, ? super T>。在这里,你使用了ArrayList::add,这给编译器无法推断出R参数的类型。

你必须在第一个参数中提供R是什么,即Supplier。你可以为类提供显式的类型参数来创建方法引用。

.collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll)

这段代码编译成功,输出结果符合预期:

2
4
16

当你使用Collectors.toList()时,只需要提供一个参数。

public static <T> Collector<T,?,List<T>> toList()

在这里,只有一个类型参数T,因此编译器可以正确推断出TInteger,因此创建了一个List<Integer>,使代码能够编译。返回的Collector的类型参数将T绑定到List<T>,使编译器能够执行类型推断。
请注意,这仅在第一次需要,因为没有目标类型可帮助进行类型推断;您继续进行流操作,最后调用System.out.println,它可以接受一个Object
如果您有以下代码:
List<Integer> modified = list.stream()
            .filter(x -> x > 0)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

然后目标类型推断会为ArrayList::new的类型参数提供Integer。这也可以编译。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接