Java 8 Iterable.forEach()与foreach循环


463

以下哪个是Java 8中的最佳实践?

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

我有很多可以用lambda简化的for循环,但是使用它们真的有任何优势吗?会提高其性能和可读性吗?

编辑

我还将这个问题扩展到更长的方法。我知道您无法从lambda返回或中断父函数,在比较它们时也应考虑到这一点,但是还有其他需要考虑的地方吗?


10
彼此之间没有真正的性能优势。第一种选择是受FP启发的东西(通常人们谈论它时更喜欢“清晰”和“清晰”的方式来表达代码)。实际上-这是“样式”问题。
尤金·洛伊

5
@Dwb:在这种情况下,这无关紧要。forEach未定义为并行或类似的东西,因此这两件事在语义上是等效的。当然,可以实现forEach的并行版本(并且标准库中可能已经存在一个并行版本),在这种情况下,lambda表达式语法将非常有用。
AardvarkSoup

8
@AardvarkSoup调用forEach的实例是一个Stream(lambdadoc.net/api/java/util/stream/Stream.html)。要请求并行执行,可以编写joins.parallel()。forEach(...)
mschenk74

13
joins.forEach((join) -> mIrc.join(mSession, join));真是的“精简” for (String join : joins) { mIrc.join(mSession, join); }?您已将标点符号计数从9增加到12,以隐藏的类型join。您真正要做的是将两个语句放在一行上。
汤姆·哈特芬

7
要考虑的另一点是Java有限的变量捕获能力。使用Stream.forEach(),您无法更新局部变量,因为它们的捕获使它们成为最终变量,这意味着您可以在forEach lambda中进行有状态的行为(除非您已准备好处理某些丑陋的操作,例如使用类状态变量)。
RedGlyph

Answers:


509

更好的做法是使用for-each。除了违反“ 保持简单,愚蠢”的原则外,这种新奇现象forEach()还至少具有以下缺陷:

  • 不能使用非最终变量。因此,无法将以下代码转换为forEach lambda:

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
  • 无法处理检查的异常。实际上并未禁止Lambda引发检查异常,但是常见的功能接口(Consumer如未声明任何异常)。因此,任何引发检查异常的代码都必须将其包装在try-catch或中Throwables.propagate()。但是即使您这样做,也不总是总是清楚抛出的异常发生了什么。它可能被吞噬在forEach()

  • 有限的流量控制。一个return在拉姆达等于continue在换每个,但没有相当于一个break。还很难执行返回值,短路或设置标志之类的操作(如果这不违反非非最终变量规则,则可能会有所减轻)。“这不仅是一种优化,而且当您考虑某些序列(例如读取文件中的行)可能有副作用,或者您可能具有无限的序列时,这一点至关重要。”

  • 可能并行执行,这对于除了需要优化的代码的0.1%之外的所有代码来说都是一件可怕的事情。任何并行代码都必须经过仔细考虑(即使它不使用锁,volatile和传统多线程执行的其他特别讨厌的方面)。任何错误都很难找到。

  • 可能会损害性能,因为JIT无法以与普通循环相同的程度优化forEach()+ lambda,尤其是现在lambda是新的。“优化”不是指调用lambda(很小)的开销,而是指现代JIT编译器对正在运行的代码执行的复杂分析和转换。

  • 如果您确实需要并行性,则使用ExecutorService可能会更快,也不会更困难。流既是自动的(阅读:对您的问题不太了解),使用专门的(阅读:在一般情况下效率不高)并行化策略(fork-join递归分解)。

  • 由于嵌套的调用层次结构以及并行执行,这使调试更加混乱。调试器可能在显示周围代码中的变量时遇到问题,并且逐步操作之类的操作可能无法按预期进行。

  • 通常,流很难编码,读取和调试。实际上,通常对于复杂的“ 流畅 ” API 都是如此。复杂的单个语句,泛型的大量使用以及缺少中间变量的组合共同产生了令人困惑的错误消息并挫败了调试工作。而不是“此方法对类型X没有重载”,您得到的错误消息更接近“在某种程度上弄乱了类型,但我们不知道在哪里或如何。” 同样,您无法像将代码分解为多个语句并将中间值保存到变量中一样轻松地在调试器中单步检查所有内容。最后,阅读代码并理解每个执行阶段的类型和行为可能并非易事。

  • 像酸痛的拇指一样伸出。Java语言已经具有for-each语句。为什么用函数调用替换它?为什么鼓励在表达式中的某处隐藏副作用?为什么鼓励笨拙的单线?将常规的for-each和new forEach混为一谈是不好的方式。代码应该用成语(由于重复而很快理解的模式)来表达,使用的成语越少,代码就越清晰,决定使用哪种成语的时间就越少(对于像我这样的完美主义者来说,这是很耗费时间的! )。

如您所见,我不喜欢forEach(),除非有必要。

对于我来说,特别令人反感的是Stream无法实现Iterable(尽管实际上具有method iterator),并且不能仅通过forEach()在for-each中使用。我建议使用将流转换为Iterables (Iterable<T>)stream::iterator。更好的选择是使用StreamEx,它解决了许多Stream API问题,包括实现Iterable

也就是说,forEach()对于以下各项很有用:

  • 以原子方式对同步列表进行迭代。在此之前Collections.synchronizedList(),对于诸如get或set之类的东西,用with 生成的列表是原子的,但是在迭代时不是线程安全的。

  • 并行执行(使用适当的并行流)。如果您的问题与Streams和Spliterators中内置的性能假设相符,则与使用ExecutorService相比,这可以节省几行代码。

  • 特定容器(如同步列表)受益于对迭代的控制(尽管这在很大程度上是理论上的,除非人们能提出更多示例)

  • 通过使用forEach()和方法引用参数(即list.forEach (obj::someMethod)更清晰地调用单个函数。但是,请记住检查异常的要点,更困难的调试以及减少编写代码时使用的惯用法数量。

我参考的文章:

编辑:看起来像一些针对lambdas的原始建议(例如http://www.javac.info/closures-v06a.html)解决了我提到的一些问题(当然,同时也增加了它们的复杂性)。


95
“为什么鼓励在表达式中的某个地方隐藏副作用?” 是错误的问题。该功能forEach是有鼓励功能的风格,即使用表达式没有副作用。如果遇到这种情况,那么forEach副作用将不能很好地发挥作用,您应该感觉到自己没有使用正确的工具进行工作。那么简单的答案是,那是因为您的感觉是正确的,因此请为此保留每个循环。经典for循环并没有被弃用……
Holger 2013年

17
@Holger如何forEach在没有副作用的情况下使用?
Aleksandr Dubinsky 2013年

14
好吧,我还不够精确,这forEach是唯一用于副作用的流操作,但是它不是针对示例代码这样的副作用,计数是一种典型的reduce操作。通常,我建议保留一个操作局部变量或影响经典for循环的控制流(包括异常处理)的操作。关于我认为的原始问题,问题源于这样的事实:有人使用流,只要for在流的源上进行简单循环就足够了。在forEach()仅适用的地方使用视频流
Holger 2013年

8
@Holger哪些副作用forEach合适的例子?
Aleksandr Dubinsky 2013年

29
可以单独处理每个项目并且不尝试对局部变量进行突变的东西。例如,操作这些项目本身或打印它们,将它们写入/发送到文件,网络流等。过滤,映射,减少,搜索和(在较小程度上)收集是流的首选操作。对于我来说,forEach看起来很方便,可以与现有的API链接。当然,对于并行操作。这些不适用于for循环。
Holger 2013年

169

当可以并行执行操作时,将考虑到这一优势。(请参阅http://java.dzone.com/articles/devoxx-2012-java-8-lambda-和 -有关内部和外部迭代的部分)

  • 从我的角度来看,主要优点是可以定义循环中要执行的操作,而不必决定是并行执行还是顺序执行

  • 如果您希望并行执行循环,则只需编写

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    您将不得不编写一些额外的代码来进行线程处理等。

注意:对于我的答案,我假设加入了实现java.util.Stream接口。如果加入仅实现java.util.Iterable接口,则不再适用。


4
他所引用的Oracle工程师的幻灯片(blogs.oracle.com/darcy/resource/Devoxx/…)没有提及这些lambda表达式内的并行性。并行性可能发生在map&之类的批量收集方法中fold,这些方法与lambda并没有真正的关系。
Thomas Jungblut 2013年

1
似乎OP的代码在这里似乎不会从自动并行化中受益(特别是因为不能保证会有一个)。我们真的不知道什么是“ mIrc”,但是“ join”似乎并不是真正可以无序释放的东西。
尤金·洛伊

10
Stream#forEach而且Iterable#forEach不是同一回事。OP正在询问Iterable#forEach
gvlasov

2
我使用了UPDATEX样式,因为在提出问题的时间与更新答案的时间之间,规范有所变化。没有答案的历史,我想这将更加令人困惑。
mschenk74

1
任何人都可以向我解释为什么如果joins实施Iterable而不是这个答案无效Stream?从我读过的几joins.stream().forEach((join) -> mIrc.join(mSession, join));joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));joinsIterable
本书中

112

阅读此问题时,您可能会觉得Iterable#forEach,与lambda表达式结合使用是编写传统的for-each循环的快捷方式/替代方法。这是不正确的。该代码来自OP:

joins.forEach(join -> mIrc.join(mSession, join));

不是打算作为写作的捷径

for (String join : joins) {
    mIrc.join(mSession, join);
}

并且当然不应以这种方式使用。相反,它旨在用作编写的快捷方式(尽管并不完全相同)

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

它是以下Java 7代码的替代品:

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

如上例所示,使用功能性接口替换循环的主体可使您的代码更明确:您要说的是:(1)循环的主体不影响周围的代码和控制流,并且(2)循环主体可以用该函数的其他实现替换,而不会影响周围的代码。无法访问外部作用域的非最终变量并不是功能/ lambda的不足,它是一种将语义与传统的for-each循环的语义区分开的功能Iterable#forEach。一旦您习惯了的语法Iterable#forEach,它就会使代码更具可读性,因为您会立即获得有关代码的其他信息。

传统的for-each循环肯定会保留Java中的优良作法(以避免过度使用术语“ 最佳实践 ”)。但这并不意味着Iterable#forEach应该将其视为不良做法或不良作风。使用正确的工具完成任务始终是一种好习惯,这包括将传统的for-each循环与混合使用Iterable#forEach,如果有意义的话。

由于Iterable#forEach已经在该线程中讨论了的缺点,因此有一些原因,为什么您可能要使用Iterable#forEach

  • 使代码更明确:如上所述,在某些情况下Iterable#forEach 可以使代码更明确和可读。

  • 要使您的代码更具可扩展性和可维护性:将函数用作循环的主体可以使您用不同的实现替换此函数(请参阅策略模式)。例如,您可以轻松地用方法调用替换lambda表达式,该方法调用可能会被子类覆盖:

    joins.forEach(getJoinStrategy());

    然后,您可以使用枚举提供默认策略,该枚举实现功能接口。这不仅使您的代码更具可扩展性,而且还提高了可维护性,因为它使循环实现与循环声明脱钩。

  • 为了使代码更易于调试:将循环实现与声明分开,还可以使调试更加容易,因为您可以使用专门的调试实现来打印调试消息,而无需使用来使主代码混乱if(DEBUG)System.out.println()。调试实施可以例如是代表,是装饰的实际功能的实现。

  • 优化性能关键代码:与该线程中的某些断言相反,至少在使用ArrayList并以“ -client”模式运行Hotspot时,Iterable#forEach 它确实比传统的for-each循环提供了更好的性能。尽管对于大多数用例而言,这种性能提升很小并且可以忽略不计,但是在某些情况下,这种额外的性能可能会有所作为。例如,如果应该将某些现有的循环实现替换为,则库维护者当然会希望进行评估Iterable#forEach

    为了支持这一事实,我使用Caliper做了一些微基准测试。这是测试代码(需要git的最新Caliper):

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    结果如下:

    当使用“ -client”运行时,Iterable#forEach在ArrayList上的性能优于传统的for循环,但仍比直接在数组上迭代慢。当使用“-服务器”运行时,所有方法的性能大致相同。

  • 为并行执行提供可选支持:在这里已经说过,Iterable#forEach使用并行执行功能接口的可能性无疑是一个重要方面。由于Collection#parallelStream()不能保证循环实际上是并行执行的,因此必须将其视为一项可选功能。通过使用遍历列表list.parallelStream().forEach(...);,您可以明确地说:该循环支持并行执行,但并不依赖于此。同样,这是一个功能,而不是缺点!

    通过将并行执行的决策从实际的循环实现中移开,可以对代码进行可选的优化,而又不影响代码本身,这是一件好事。另外,如果默认的并行流实现不符合您的需求,那么没有人会阻止您提供自己的实现。例如,您可以根据基础操作系统,集合的大小,内核数以及某些首选项设置来提供优化的集合:

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    这里的好处是,您的循环实现无需了解或关心这些细节。


5
您在此讨论中有一个有趣的观点,并提出了许多要点。我将尝试解决它们。您建议之间进行切换forEachfor-each某些标准有关循环体的性质为主。遵循此类规则的智慧和纪律是优秀程序员的标志。这样的规则也是他的祸根,因为他周围的人要么不遵守,要么不同意。例如,使用已检查异常与未检查异常。这种情况似乎更加细微。但是,如果主体“不影响环绕代码或流控制”,是不是将其分解为一个更好的函数?
Aleksandr Dubinsky 2014年

4
感谢Aleksandr的详细评论。But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?。是的,我认为通常是这种情况-将这些循环分解为函数是很自然的结果。
Balder 2014年

2
关于性能问题-我想这很大程度上取决于循环的性质。在我正在从事的项目中,Iterable#forEach由于性能提高,我一直在使用类似于Java 8之前的函数式循环。所讨论的项目有一个类似于游戏循环的主循环,具有未定义数量的嵌套子循环,客户端可以在其中将循环参与者作为函数插入。这样的软件结构极大地受益于Iteable#forEach
Balder 2014年

7
在我的评论的最后,有一句话:“代码应该用成语说话,使用的成语越少,代码就越清晰,决定使用哪种成语的时间就越少”。当我从C#切换到Java时,我开始深深地体会到这一点。
Aleksandr Dubinsky 2014年

7
那是一个不好的说法。您可以使用它来证明您想要的任何东西:为什么不应该使用for循环,因为while循环就足够了,这比习惯用法少了。哎呀,当goto可以做更多的事情时,为什么还要使用任何循环,切换或try / catch语句。
tapichu 2014年

13

forEach()可以实现为比for-each循环更快,因为与标准迭代器方法相反,可迭代器知道迭代其元素的最佳方法。所以区别是内部循环或外部循环。

例如ArrayList.forEach(action)可以简单地实现为

for(int i=0; i<size; i++)
    action.accept(elements[i])

与for-each循环相反,后者需要大量的脚手架

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

但是,我们还需要使用来考虑两个间接费用forEach(),一个是制作lambda对象,另一个是调用lambda方法。它们可能并不重要。

另请参阅http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/,以比较不同用例的内部/外部迭代。


8
为什么迭代器知道最好的方法,但是迭代器却不知道呢?
mschenk74

2
没有本质上的区别,但是需要额外的代码来符合迭代器接口,这可能会更加昂贵。
ZhongYu

1
@ zhong.j.yu如果您实现Collection,则无论如何也要实现Iterable。因此,如果您要这样做,那么在“添加更多代码以实现缺少的接口方法”方面就没有代码开销。正如mschenk74所说,您似乎没有理由不调整迭代器以知道如何以最佳方式遍历集合。我确实同意创建迭代器可能会产生开销,但是说真的,那些东西通常是如此便宜,以至于您可以说它们的成本为零...
Eugene Loy

4
例如,迭代一棵树:void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);},这比外部迭代更为优雅,您可以决定如何最好地同步
棘手怪胎

1
有趣的是,String.join方法中唯一的注释(好的,错误的连接)是“不太可能值得Arrays.stream开销的元素数量”。所以他们使用豪华的循环。
Tom Hawtin-大头钉

7

TL; DRList.stream().forEach()是最快的。

我觉得我应该添加基准测试迭代的结果。我采用了一种非常简单的方法(没有基准测试框架),并基准了5种不同的方法:

  1. 经典 for
  2. 经典foreach
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

测试程序和参数

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

此类中的列表应进行迭代,并且doIt(Integer i)每次都通过不同的方法将其应用于其所有成员。在Main类中,我运行了经过测试的方法三次以预热JVM。然后,我将测试方法运行1000次,计算每种迭代方法所需的时间(使用System.nanoTime())。完成之后,我将总和除以1000,得出的结果是平均时间。例:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

我在具有Java版本1.8.0_05的i5 4核心CPU上运行了该程序

经典 for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

执行时间:4.21毫秒

经典foreach

for(Integer i : list) {
    doIt(i);
}

执行时间:5.95毫秒

List.forEach()

list.forEach((i) -> doIt(i));

执行时间:3.11毫秒

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

执行时间:2.79毫秒

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

执行时间:3.6毫秒


23
您如何获得这些数字?您正在使用哪个基准测试框架?如果您不使用任何内容而只是简单System.out.println地天真地显示此数据,则所有结果都是无用的。
Luiggi Mendoza 2015年

2
没有框架。我用System.nanoTime()。如果您阅读了答案,您将看到它是如何完成的。我认为这不是一个相对的问题,因此没有用。我不在乎某个方法的效果如何,我在乎与其他方法相比效果如何。
阿萨夫2015年

31
这就是一个好的微基准测试的目的。由于您没有满足这些要求,因此结果是无用的。
Luiggi Mendoza 2015年

6
我可以建议您去了解JMH,这是Java本身正在使用的方法,并且花了很多精力来获取正确的数字:openjdk.java.net/projects/code-tools/jmh
dsvensson

1
我同意@LuiggiMendoza。无法知道这些结果是否一致或有效。上帝知道我已经做了多少个基准测试,它们不断报告不同的结果,尤其是取决于迭代顺序,大小以及没有的结果。
毫米

6

我觉得我需要补充一点评论...

关于范式\样式

这可能是最值得注意的方面。FP之所以受欢迎,是因为您可以避免副作用。由于这与问题无关,因此我不会深入研究可以从中获得哪些利弊。

但是,我会说使用Iterable.forEach进行的迭代是受FP启发的,而是将更多FP引入Java的结果(具有讽刺意味的是,我想说,纯FP中forEach并没有太多用处,因为除了引入外,它没有任何作用副作用)。

最后,我要说的是,这只是您当前正在编写的“品味\样式\范例”问题。

关于并行性。

从性能的角度来看,与foreach(...)相比,使用Iterable.forEach不会带来任何明显的好处。

根据Iterable.forEach的官方文档

对Iterable的内容执行给定的操作,迭代时元素出现的顺序,直到所有元素都已处理或该操作引发异常为止。

...即文档非常清楚,不会存在隐式并行性。添加一个将违反LSP。

现在,在Java 8中已承诺有“并行集合”,但是要与我一起使用,您需要更加明确,并格外小心地使用它们(例如,请参阅mschenk74的答案)。

顺便说一句:在这种情况下,将使用Stream.forEach,它不能保证实际工作将并行进行(取决于基础集合)。

更新:可能不那么明显,乍一看有点张扬,但还有样式和可读性方面的另一个方面。

首先-普通的旧forloop既普通又旧。每个人都已经认识他们。

其次,也是更重要的一点-您可能只想将Iterable.forEach与单层lambda一起使用。如果“身体”变重-他们往往不那么可读。从这里有2个选项-使用内部类(yuck)或使用普通的旧forloop。当人们在相同的代码库中看到相同的事情(集合上的迭代器)时,常常会感到烦恼,这似乎是事实。

同样,这可能是问题,也可能不是问题。取决于从事代码工作的人员。


1
并行不需要新的“并行集合”。这仅取决于您是否要求一个固定的流(使用collection.stream())或并行的流(使用collection.parallelStream())。
JB Nizet

@JBNizet根据docs Collection.parallelStream()不保证实现的集合将返回并行流。我实际上是在想自己,什么时候会发生这种情况,但是可能确实取决于收集。
尤金·洛伊

同意 这也取决于收藏。但是我的意思是,所有标准集合(ArrayList等)都已经可以使用并行的foreach循环。无需等待“并行收集”。
JB Nizet

@JBNizet同意您的观点,但这并不是我最初所说的“并行集合”真正的意思。我引用Collection.parallelStream(),它在Java 8中被添加为“并行集合”,类似于与Scala的概念相似的东西。另外,不确定如何用JSR调用它,我看到几篇论文针对此Java 8功能使用了相同的术语。
尤金·洛伊

1
对于最后一段,您可以使用函数引用:collection.forEach(MyClass::loopBody);
棘轮怪胎

6

该功能最大forEach的局限性之一是缺少检查异常支持。

一种可能的解决方法是将终端替换forEach为普通的旧foreach循环:

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

以下是有关lambda和流中检查异常处理的其他解决方法的最受欢迎的问题列表:

抛出异常的Java 8 Lambda函数?

Java 8:Lambda-Streams,按方法进行过滤(异常)

如何从Java 8流中引发CHECKED异常?

Java 8:lambda表达式中的强制检查异常处理。为什么是强制性的而不是可选性的?


3

与1.7增强的for循环相比,Java 1.8 forEach方法的优势在于,在编写代码时,您只能专注于业务逻辑。

forEach方法将java.util.function.Consumer对象作为参数,因此它有助于将我们的业务逻辑放在单独的位置,您可以随时重复使用它。

看下面的代码片段,

  • 在这里,我创建了新类,该类将覆盖Consumer Class中的accept类方法,您可以在其中添加其他功能,而不是Iteration .. !!!!!!

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
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.