在返回值中替换Collection for Stream是一个好主意吗?

时间:2015-02-16 11:20:36

标签: lambda java-8 java-stream api-design

直到Java 8,表示元素集合的属性通常返回Collection。在缺少不可变集合接口的情况下,常见的习惯用法是将其包装为:

Collection<Foo> getFoos(){ return Collections.unmodifiableCollection(foos); }

现在Stream就在这里,很有可能开始暴露Streams而不是Collections。

我看到的好处:

  1. 真正不可变的API
  2. 通常情况下,此类属性的客户端对查询或迭代结果感兴趣(如果要对集合进行更新,那将非常糟糕。)。
  3. 另一方面, Streams只能使用一次,并且不能像常规集合那样传递。这尤其令人担忧。

    这个问题与similar looking question不同,因为它更广泛,因为OP明确表示他打算返回的流不会被传递。在我看来,这个方面没有在原始问题的答案中得到解决。

    换句话说:在我看来,如果API返回一个流,那么一般的思维方式应该是与它的所有交互都必须在直接上下文中终止。应该禁止传递流。

    但是,除非开发人员非常熟悉Stream API,否则似乎很难执行。这意味着这种API需要范式转换。我对这个断言是对的吗?

2 个答案:

答案 0 :(得分:3)

让我提出一个简单的规则:

  

作为方法参数传递或作为方法的返回值返回的Stream必须是未终止的管道的尾部。

对于我们这些从事过流程工作的人来说,这可能是非常明显的,我们从不打算将其写下来。但对于第一次接近溪流的人来说,这可能并不明显,因此值得讨论。

主要规则包含在Streams API package documentation中:一个流最多只能有一个终端操作。一旦终止,添加任何中间或终端操作都是非法的。

另一条规则是流管道必须是线性的;他们不能有分支机构。这并没有明确记录,但在Stream class documentation约有三分之二的时间内提到了它。这意味着如果不是管道上的最后一个操作,则向流添加中间或终端操作是非法的。

大多数流方法是中间操作或终端操作。如果您尝试在已终止的流中使用其中一个或不是最后一个操作,则可以通过获取IllegalArgumentException来快速找到。偶尔会发生这种情况,但我认为一旦人们认为管道必须是线性的,他们就会学会避免这个问题,问题就会消失。我认为这对大多数人来说很容易掌握;它不应该要求范式转换。

一旦理解了这一点,很明显,如果您要将Stream实例交给另一段代码 - 或者将其作为参数传递,或者将其返回给调用者 - 它需要是流源或管道中的最后一个中间操作。也就是说,它需要成为未端接管道的尾部。

  

换句话说:在我看来,如果API返回一个流,那么一般的思维方式应该是与它的所有交互都必须在直接上下文中终止。应该禁止传递流。

我认为这个限制性太强了。只要你遵守我提出的规则,你就可以自由地传递你想要的流。实际上,有很多用例可以从某个地方获取流,修改它并传递它。以下是几个例子。

1)打开一个文本文件,其中包含每行上POJO的文本表示。致电File.lines()以获得Stream<String>。将每一行映射到POJO实例,并将Stream<POJO>返回给调用者。调用者可以应用过滤器或排序操作,并将流返回给其调用者。

2)给定Stream<POJO>,您可能希望拥有一个Web界面,以允许用户提供一组复杂的搜索条件。 (例如,考虑一个包含大量排序和过滤选项的购物网站。)您可能拥有如下方法,而不是在代码中组合大型复杂管道:

Stream<POJO> applyCriteria(Stream<POJO>, SearchCriteria)

将采用流,通过附加各种过滤器来应用搜索条件,以及可能的排序或不同操作,并将结果流返回给调用者。

从这些示例中,我希望您可以看到传递流的灵活性相当大,只要您传递的内容始终是未终止流水线的尾部。

答案 1 :(得分:0)

取决于

如果您确实从方法中返回Streams,那么始终需要确保在返回时它们尚未关闭。

在应用程序API中使用Streams会增加应用程序用户也会传递Streams而不是Collections的可能性 - 这意味着他们还需要记住他们不应该返回已经关闭的Streams。

在使用Streams的私人项目中可能会有效,但如果您正在构建公共API,我不会考虑将Streams作为一个好主意。

就个人而言,我更喜欢使用Iterables来支持收藏,因为它们具有不变性。我创建了一个名为Enumerables的包装器,使用与Stream类似的功能API扩展Iterable。