0

发现 collect(Collectors.toList()) 和 Stream.toList() 之间的区别。看

class Animal { }
class Cat extends Animal { }
record House(Cat cat) { }

class Stuff {
    public static void function() {
        List<House> houses = new ArrayList<>();
        List<Animal> animals1 = 
            houses.stream()
                  .map(House::cat)
                  .collect(Collectors.toList()); // ok
        List<Animal> animals2 =
            houses.stream()
                  .map(House::cat).toList(); // compile error
        List<Animal> animals3 =
            houses.stream()
                  .map(House::cat)
                  .map(cat -> (Animal) cat).toList(); // ok
    }
}

collect(Collectors.toList()) 能够给我一个动物列表或猫列表。但是 Stream.toList() 只能给出 Cat 的列表。

问题是有什么方法可以让 Stream.toList() 工作。在我的真实示例中,我有一个覆盖shutdownNow 的类,它返回一个Runnable 列表,所以我的类调用something.stream().collect(Collectors.toList()),但是something.stream().toList()返回 MyRunnable 的列表。

我的一部分希望他们将函数声明为default <U super T> List<U> toList()而不是default List<T> toList(),尽管奇怪的是这是我机器上的编译错误(我的编译器似乎可以使用 U extends T,而不是 U super T)。

4

2 回答 2

6

这里有一个简单的答案。

从...开始

var stream = houses.stream().map(House::cat)

在这里,stream有类型Stream<Cat>。该Stream::toList方法为您提供了流元素类型的列表,此处为Cat. stream.toList()类型也是如此List<Cat>。这里没有选择。

Collector有多个类型变量,包括输入元素的类型和输出结果的类型。Collector创建一个接受Cat和产生List<Cat>, List<Animal>,等的 a 有很大的灵活性Set<Animal>。这种灵活性部分地被Stream::collect(以及泛型方法Collectors::toList)的通用性所隐藏;推断此方法的泛型类型参数可以考虑 LHS 上所需的结果类型。Cat所以语言论文为你和你之间的差距Animal,因为在流和结果之间存在另一个层次的间接性。

正如@Eugene 指出的那样,您可以获得更通用的类型:

List<? extends Animal> animals2 = houses.stream().map(House::cat).toList()

这与流无关;只是因为List<? extends Animal>是 的超类型List<Cat>。但是有一个更简单的方法。如果您想要 aList<Animal>并且要使用toList(),请将流类型更改为 a Stream<Animal>

List<Animal> animals = houses.stream().map(h -> (Animal) h.cat()).toList()

Stream::map也是通用的,所以通过将 lambda 的 RHS 设置为Animal, not Cat,你会得到一个Stream<Animal>out ,然后toList()给你一个List<Animal>.

如果您更喜欢,也可以将其拆分:

List<Animal> animals = houses.stream().map(House::cat)
                                      .map(c -> (Animal) c).toList()

让你失望的是,因为collect是一个泛型方法(等等Collectors::toList),有额外的灵活性来推断一个稍微不同的类型,而在更简单的流中,一切都更加明确,所以如果你想调整类型,你必须在命令式代码中做到这一点。

于 2021-09-12T13:22:27.847 回答
3

这是不可能实现的。

Collectors::toList有一个? extends T. 另一方面Stream::toList返回 a List<T>,你被卡住了。

您可以通过以下方式(部分)解决该问题:

List<? extends Animal> animals2 = houses.stream().map(House::cat).toList();

您在想的是U super T,但不幸的是,这不受支持。人们偶尔会为它找到很好的真实用例——但没有支持。

于 2021-09-11T23:41:08.423 回答