我应该退回收藏夹还是流?

假设我有一个方法,它将只读视图返回到成员列表中:

class Team {
    private List<Player> players = new ArrayList<>();

    // ...

    public List<Player> getPlayers() {
        return Collections.unmodifiableList(players);
    }
}

进一步假设客户端所做的只是立即迭代列表一次。也许把玩家放到一个JList或别的什么地方。客户端不会存储对列表的引用以供以后检查!

鉴于此常见情况,我是否应该返回流?

public Stream<Player> getPlayers() {
    return players.stream();
}

还是在Java中返回非惯用语流?流是否设计为始终在创建它们的同一表达式中“终止”?


答案 1

答案是,一如既往,“视情况而定”。这取决于返回的集合的大小。这取决于结果是否随时间而变化,以及返回结果的一致性有多重要。这在很大程度上取决于用户可能如何使用答案。

首先,请注意,您始终可以从 a 获取 a,反之亦然:CollectionStream

// If API returns Collection, convert with stream()
getFoo().stream()...

// If API returns Stream, use collect()
Collection<T> c = getFooStream().collect(toList());

所以问题是,哪个对你的来电者更有用。

如果您的结果可能是无限的,则只有一个选择:.Stream

如果您的结果可能非常大,您可能更喜欢 ,因为一次实现所有结果可能没有任何价值,这样做可能会产生巨大的堆压力。Stream

如果调用方要做的就是循环访问它(搜索,过滤器,聚合),您应该更喜欢,因为这些已经内置了,并且不需要具体化集合(特别是如果用户可能不处理整个结果)。这是一个非常常见的情况。StreamStream

即使您知道用户将多次迭代它或以其他方式保留它,您仍然可能希望返回一个,因为一个简单的事实是,无论您选择将其放入什么(例如,)可能不是他们想要的形式,然后调用方无论如何都必须复制它。如果你返回 一个 ,他们可以做,并以他们想要的形式得到它。StreamCollectionArrayListStreamcollect(toCollection(factory))

上述“首选”案例大多源于更灵活的事实;您可以延迟绑定到使用它的方式,而不会产生将其具体化为.StreamStreamCollection

必须返回 a 的一种情况是,当存在强一致性要求时,您必须生成移动目标的一致快照。然后,您需要将元素放入不会更改的集合中。Collection

所以我想说的是,大多数时候,是正确的答案 - 它更灵活,它不会施加通常不必要的物化成本,并且可以在需要时轻松转换为您选择的集合。但有时,您可能必须返回(例如,由于强一致性要求),或者您可能希望返回,因为您知道用户将如何使用它,并且知道这对他们来说是最方便的事情。StreamCollectionCollection

如果您已经有一个合适的“躺着”,并且您的用户似乎更愿意将其作为与它进行交互,那么返回您拥有的东西是一个合理的选择(尽管不是唯一的选择,而且更脆弱)。CollectionCollection


答案 2

对于Brian Goetz的出色回答,我有几点要补充。

从“getter”样式的方法调用返回 Stream 是很常见的。请参阅 Java 8 javadoc 中的流用法页面,并查找“方法...返回流“,用于 除 以外的包。这些方法通常位于表示或可以包含多个值或聚合的类上。在这种情况下,API 通常返回它们的集合或数组。由于 Brian 在回答中指出的所有原因,在此处添加流返回方法非常灵活。其中许多类已经具有集合或数组返回方法,因为这些类早于 Streams API。如果您正在设计新的 API,并且提供流返回方法很有意义,则可能也不必添加集合返回方法。java.util.Stream

Brian提到了将值“具体化”到集合中的成本。为了放大这一点,这里实际上有两个成本:在集合中存储值的成本(内存分配和复制),以及首先创建值的成本。后一种成本通常可以通过利用Stream的懒惰寻求行为来降低或避免。一个很好的例子是以下中的 API:java.nio.file.Files

static Stream<String>  lines(path)
static List<String>    readAllLines(path)

不仅要将整个文件内容保存在内存中才能将其存储到结果列表中,还必须在返回列表之前将文件读取到最后。该方法在执行一些设置后几乎可以立即返回,将文件读取和换行留到以后需要时 - 或者根本不需要。这是一个巨大的好处,例如,如果调用方只对前十行感兴趣:readAllLineslines

try (Stream<String> lines = Files.lines(path)) {
    List<String> firstTen = lines.limit(10).collect(toList());
}

当然,如果调用方过滤流以仅返回与模式匹配的行,则可以节省相当大的内存空间,等等。

一个似乎正在出现的成语是,在它表示或包含的事物名称的复数形式之后命名流返回方法,而不带前缀。此外,虽然当只有一组可能要返回的值时,它是流返回方法的合理名称,但有时有些类具有多种类型的值的聚合。例如,假设您有一个同时包含属性和元素的对象。您可以提供两个流返回 API:getstream()

Stream<Attribute>  attributes();
Stream<Element>    elements();