首页 文章

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

提问于
浏览
141

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

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中返回非惯用的流?设计的流是否始终在它们创建的同一表达式中“终止”?

8 回答

  • 62

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

    首先,请注意您始终可以从Stream获取集合,反之亦然:

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

    所以问题是,这对你的来电者更有用 .

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

    如果你的结果可能非常大,你可能更喜欢Stream,因为一次实现它可能没有任何 Value ,这样做会造成很大的堆压力 .

    如果所有调用者都要迭代它(搜索,过滤,聚合),你应该更喜欢Stream,因为Stream已经内置了这些内置并且不需要实现集合(特别是如果用户可能不处理整个结果 . )这是一个非常常见的情况 .

    即使您知道用户将多次迭代它或以其他方式保留它,您仍然可能想要返回一个Stream,因为简单的事实是您选择将其放入的任何Collection(例如,ArrayList)可能不是他们想要的形式,然后调用者无论如何都要复制它 . 如果你返回一个流,他们可以做 collect(toCollection(factory)) 并以他们想要的形式获得它 .

    上述“偏好流”案例主要源于Stream更灵活的事实;您可以延迟绑定到如何使用它,而不会产生将其具体化为集合的成本和约束 .

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

    所以我想说大多数时候,Stream是正确的答案 - 它更灵活,它不会强加通常不必要的物化成本,并且如果需要可以很容易地变成你选择的集合 . 但有时,您可能必须返回一个Collection(例如,由于强一致性要求),或者您可能想要返回Collection,因为您知道用户将如何使用它并且知道这对他们来说是最方便的 .

  • 0

    我有几点要加到Brian Goetz' excellent answer .

    从"getter"样式方法调用返回Stream是很常见的 . 请参阅Java 8 javadoc中的Stream usage page,并查找"methods... that return Stream"以获取除 java.util.Stream 以外的软件包 . 这些方法通常位于表示或可包含多个值或某些聚合的类上 . 在这种情况下,API通常会返回集合或它们的数组 . 由于Brian在他的回答中指出的所有原因,它正在设计一个新的API,并且提供流返回方法是有意义的,也可能没有必要添加收集返回方法 .

    Brian将"materializing"值的成本提到了一个集合中 . 为了放大这一点,这里实际上有两个成本:在集合中存储值的成本(内存分配和复制)以及首先创建值的成本 . 通过利用Stream的懒惰行为,通常可以减少或避免后者的成本 . 一个很好的例子是 java.nio.file.Files 中的API:

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

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

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

    当然,如果调用者过滤流仅返回,则可以节省大量的内存空间与图案匹配的线条等

    似乎正在出现的一个习惯用法是在它表示或包含的事物的复数名称之后命名流返回方法,而没有 get 前缀 . 此外,虽然 stream() 是流返回方法的合理名称,但只有一组可能返回的值,有时会有类具有多种类型值的聚合 . 例如,假设您有一些包含属性和元素的对象 . 您可以提供两个流返回API:

    Stream<Attribute>  attributes();
    Stream<Element>    elements();
    
  • -4

    在团队/企业/可重用类上下文中:

    通常是设计类,而不考虑客户可能对该类假设的内容 . 如果返回Stream,则返回可能无限的列表,客户端应将此视为可能无限列表 . 这是违反代码安全性和任何其他类或其他方法的良好品味在它自己没有创建的流上运行 stream.collect(toList()) ,盲目地假设它是有限的 . 如果这是一个理想的代码模式,那么流应该有一些像 isFinite() 这样的方法来防止错误的假设 .

    更糟糕的是,返回Stream以避免实现同样糟糕,因为使用 .collect() 来实现流似乎太容易了,这意味着当服务类避免实现时,客户端类可能会以高成本进行操作,特别是如果你作为代码样式一直返回流,并且不能再告诉应该实现哪些流返回以及哪些流不应该实现 . 提示实现不好的经典接口是java.util.Iterator(即使commons-lang定义了IteratorUtils.toList()) .

    因此,我建议使用任何非收集方法返回,只有当您想要向客户说明结果可能是无限的或可能不适合实现时 .

    那些流更方便的函数式编程并不意味着它们应该是首选的,这意味着JDK应该扩展它's collection API to allow for the same operations, so that clients don' t必须调用 collection.stream().... .

    用于家庭作业,严格的个人项目或其他未重复使用的代码

    从单纯的算法角度来看,当忽略代码风格,可读性或OO-Design问题时,在接受的答案中更喜欢Streams是可以的 . 但我不知道如何在StackOverflow上做出一般性建议

  • 1

    流被设计为始终在它们创建的同一表达式中“终止”吗?

    这就是大多数例子中使用它们的方式 .

    注意:返回一个Stream与返回Iterator没有什么不同(承认具有更强的表现力)

    恕我直言,最好的解决方案是封装你为什么这样做,而不是返回集合 .

    例如

    public int playerCount();
    public Player player(int n);
    

    或者如果你打算计算它们

    public int countPlayersWho(Predicate<? super Player> test);
    
  • 2

    如果流是有限的,并且对返回的对象有预期/正常操作,这将抛出一个已检查的异常,我总是返回一个Collection . 因为如果你要在每个可以抛出检查异常的对象上做某事,你就会讨厌这个流 . 一个真正的缺乏流我无法优雅地处理检查异常 .

    现在,也许这表明您不需要经过检查的例外,这是公平的,但有时它们是不可避免的 .

  • 0

    我认为这取决于你的情况 . 可能是,如果你让 Team 实现 Iterable<Player> ,就足够了 .

    for (Player player : team) {
        System.out.println(player);
    }
    

    或者是一种功能风格:

    team.forEach(System.out::println);
    

    但是如果你想要一个更完整和流畅的api,一个流可能是一个很好的解决方案 .

  • 194

    也许Stream工厂将是更好的选择 . 仅通过Stream公开集合的最大胜利是它更好地封装了您的域模型的数据结构 . 只要暴露一个Stream,就不可能使用你的域类来影响List或Set的内部工作 . 它还鼓励您的域类用户以更现代的Java 8风格编写代码 . 通过保留现有的getter并添加新的Stream-returns getter,可以逐步重构此样式 . 随着时间的推移,您可以重写遗留代码,直到最终删除所有返回List或Set的getter . 一旦你清除了所有遗留代码,这种重构感觉非常好!

  • 1

    我可能有2个方法,一个返回 Collection ,另一个返回集合 Stream .

    class Team
    {
        private List<Player> players = new ArrayList<>();
    
    // ...
    
        public List<Player> getPlayers()
        {
            return Collections.unmodifiableList(players);
        }
    
        public Stream<Player> getPlayerStream()
        {
            return players.stream();
        }
    
    }
    

    这是两全其美的 . 该客户端可以选择是否需要List或Stream,并且他们不必为了获得Stream而创建额外的对象创建列表的不可变副本 .

    这也只为您的API增加了一个方法,因此您没有太多方法

相关问题