为什么要使用“函数操作”而不是for循环?


39
for (Canvas canvas : list) {
}

NetBeans建议我使用“功能操作”:

list.stream().forEach((canvas) -> {
});

但是,这是为什么首选?如果有的话,很难阅读和理解。您正在调用stream(),然后forEach()使用带参数的lambda表达式canvas。我看不出有什么比for第一个片段中的循环更好。

显然,我只是出于美学目的。也许这里缺少我的技术优势。它是什么?为什么我应该改用第二种方法?



15
在您的特定示例中,它不是首选。
罗伯特·哈维

1
只要唯一的操作是forEach,我倾向于同意您的看法。一旦将其他操作添加到管道中,或者产生了输出序列,流方法就变得可取了。
JacquesB 2015年

@RobertHarvey不是吗?为什么不?
萨拉

@RobertHarvey好吧,我同意被接受的答案确实表明了在更复杂的情况下for-version是如何被淘汰的,但是我不明白为什么在琐碎的情况下“胜出”。您说这是不言而喻的,但我看不到,所以我问。
萨拉

Answers:


45

对于要在传入的数据集或数据流上进行的各种操作的组合,流提供了更好的抽象。尤其是当您需要映射元素,过滤和转换它们时。

您的例子不是很实际。考虑以下来自Oracle网站的代码

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

可以使用流编写:

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

第二个选项更具可读性。因此,当您有嵌套循环或执行部分处理的各种循环时,它非常适合使用Streams / Lambda API。


5
有一些优化技术,例如地图融合stream.map(f).map(g)stream.map(f.andThen(g)))生成/减少融合(当以一种方法构建流然后将其传递给消耗它的另一种方法时,编译器可以消除该流)和流融合(可以融合许多流操作)合并成一个命令式循环),这可以使流操作更加高效。它们在GHC Haskell编译器中以及在其他一些Haskell和其他功能语言编译器中实现,并且有针对Scala的实验研究实现。
约尔格W¯¯米塔格

在考虑功能性vs循环时,性能可能不是一个因素,因为如果Netbeans可以确定编译器可以/应该将其从for循环转换为功能性操作,并且确定它是最佳路径,则性能可能不是。
Ryan

我不同意第二点更具可读性。需要一段时间才能弄清楚发生了什么。使用第二种方法是否有性能优势,否则我看不到它?
Bok McDonagh

@BokMcDonagh,我的经验是,对于那些不费心去熟悉新抽象的开发人员来说,它的可读性较差。我建议更多地使用此类API,以使其更加熟悉,因为这是未来。不仅在Java世界中。
luboskrnac

16

使用功能性流API的另一个优点是,它隐藏了实现细节。它仅描述应该做什么,而不是如何做。当查看需要进行的更改以从单线程执行变为并行代码执行时,此优势变得显而易见。只需将更.stream()改为即可.parallelStream()


13

如果有的话,很难阅读和理解。

那是非常主观的。我发现第二个版本更容易阅读和理解。它与其他语言(例如Ruby,Smalltalk,Clojure,Io,Ioke,Seph)的语言匹配,需要更少的概念来理解(这是与其他语言一样的普通方法调用,而第一个示例是专用语法)。

如果有的话,这是一个熟悉的问题。


6
对,是真的。但这看起来更像是评论,而不是对我的答复。
欧米茄

功能性操作也使用专门的语法:“->”是新的
Ryan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.