为了账号安全,请及时绑定邮箱和手机立即绑定

为什么Java Streams一次性出现?

为什么Java Streams一次性出现?

白猪掌柜的 2019-10-23 14:34:10
与C#不同IEnumerable,在C#中,执行管道可以执行任意多次,而在Java中,流只能“迭代”一次。对终端操作的任何调用都会关闭流,使其无法使用。这种“功能”带走了很多力量。我想这不是技术原因。这种奇怪的限制背后的设计考虑是什么?编辑:为了演示我在说什么,请考虑以下C#中的Quick-Sort实现:IEnumerable<int> QuickSort(IEnumerable<int> ints){  if (!ints.Any()) {    return Enumerable.Empty<int>();  }  int pivot = ints.First();  IEnumerable<int> lt = ints.Where(i => i < pivot);  IEnumerable<int> gt = ints.Where(i => i > pivot);  return QuickSort(lt).Concat(new int[] { pivot }).Concat(QuickSort(gt));}现在确定,我不主张这是快速排序的良好实现!但是,这是将lambda表达与流操作相结合的表达能力的一个很好的例子。而且它不能用Java完成!我什至不能问一个流是否为空而不使它无法使用。
查看完整描述

3 回答

?
缥缈止盈

TA贡献2041条经验 获得超4个赞

Streams是围绕Spliterators 建立的,s是有状态的可变对象。他们没有“重置”动作,实际上,要求支持这种倒带动作将“夺走很多力量”。怎么会Random.ints()被认为来处理这样的要求?


另一方面,对于Stream具有可追溯原点的,很容易构造等效项Stream以再次使用。只需将构成的步骤Stream放入可重用的方法即可。请记住,重复这些步骤并不是昂贵的操作,因为所有这些步骤都是惰性操作。实际的工作从终端操作开始,并且取决于实际的终端操作,可能会执行完全不同的代码。


这种方法的作者将由您自己来指定两次调用该方法所隐含的含义:该方法是否再现与为未修改的数组或集合创建的流完全相同的序列,或者它会产生带有语义相似,但元素不同,例如随机整数流或控制台输入行流等。


顺便说一下,为了避免混淆,终端操作消耗的Stream是从不同的闭合的Stream作为调用close()流上做(这是需要的具有相关联的资源,如通过产生,例如流Files.lines())。


这似乎很混乱,从误导的比较茎IEnumerable用Stream。An IEnumerable表示提供实际值的能力IEnumerator,因此类似于IterableJava中的。相反,a Stream是一种迭代器,可与a媲美,IEnumerator因此断言这种数据类型可以在.NET中多次使用是错误的,对此的支持IEnumerator.Reset是可选的。这里讨论的示例使用了一个事实,IEnumerable可以使用an 来获取new, IEnumerator并且该Java也可以与Java一起使用Collection。你可以得到一个新的Stream。如果Java开发人员决定直接将Stream操作添加到Iterable,中间操作将返回另一个操作Iterable,它确实具有可比性,并且可以以相同的方式工作。


但是,开发人员对此表示反对,并且在此问题中讨论了该决定。最大的问题是关于急切的Collection操作和惰性Stream操作的困惑。通过查看.NET API,我(是的,个人而言)发现它是合理的。虽然IEnumerable单独看看上去很合理,但是特定的Collection将有很多直接操作Collection的方法,并且有许多方法返回lazy IEnumerable,而方法的特殊性质并不总是可以直观地识别出来的。我发现(在我看了几分钟后)最糟糕的例子是,List.Reverse()它的名称与继承的名称完全匹配(对于扩展方法,这是正确的终点吗?),Enumerable.Reverse()却具有完全矛盾的行为。


当然,这是两个不同的决定。第一个使Stream类型不同于Iterable/ 的类型Collection,第二个使Stream一种一次性迭代器而不是另一种可迭代。但是这些决定是一起做出的,可能是从未考虑过将这两个决定分开考虑的情况。创建它的初衷并不是与.NET相提并论。


API的实际设计决定是添加改进的迭代器类型Spliterator。Spliterators可以由旧的Iterables(这是对它们进行改装的方式)或全新的实现来提供。然后,Stream作为高级前端添加到了较低的Spliterators中。而已。您可能会讨论不同的设计是否会更好,但是考虑到现在的设计方式,这不会提高生产力,也不会改变。


您还需要考虑另一个实现方面。Streams 不是不变的数据结构。每个中间操作都可以返回一个Stream封装了旧实例的新实例,但它也可以替代地操纵自己的实例并返回自己(这并不排除对同一操作都执行)。众所周知的示例是类似parallel或的操作unordered,它们不会添加其他步骤,而是会操纵整个管道。具有如此可变的数据结构并尝试重用(或者更糟的是,同时使用多次)效果不佳……


为了完整起见,这是您的快速排序示例,已转换为Java StreamAPI。它表明它并没有真正“夺走很多能量”。


static Stream<Integer> quickSort(Supplier<Stream<Integer>> ints) {


  final Optional<Integer> optPivot = ints.get().findAny();

  if(!optPivot.isPresent()) return Stream.empty();


  final int pivot = optPivot.get();


  Supplier<Stream<Integer>> lt = ()->ints.get().filter(i -> i < pivot);

  Supplier<Stream<Integer>> gt = ()->ints.get().filter(i -> i > pivot);


  return Stream.of(quickSort(lt), Stream.of(pivot), quickSort(gt)).flatMap(s->s);

}

它可以像


List<Integer> l=new Random().ints(100, 0, 1000).boxed().collect(Collectors.toList());

System.out.println(l);

System.out.println(quickSort(l::stream)

    .map(Object::toString).collect(Collectors.joining(", ")));

您可以将其编写得更加紧凑


static Stream<Integer> quickSort(Supplier<Stream<Integer>> ints) {

    return ints.get().findAny().map(pivot ->

         Stream.of(

                   quickSort(()->ints.get().filter(i -> i < pivot)),

                   Stream.of(pivot),

                   quickSort(()->ints.get().filter(i -> i > pivot)))

        .flatMap(s->s)).orElse(Stream.empty());

}


查看完整回答
反对 回复 2019-10-23
  • 3 回答
  • 0 关注
  • 536 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信