Java 8中的Iterable.forEach()和foreach循环有什么区别?

549

以下哪一项是Java 8中更好的实践方法?

Java 8:

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

Java 7:

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

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

编辑

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


15
两种方法并没有真正的性能优劣之分。第一种选项受到FP启发(这通常被认为是一种更“好看”和“清晰”的表达代码的方式)。实际上,这是一个更多关于“风格”的问题。 - Eugene Loy
5
在这种情况下,这并不相关。forEach没有被定义为并行或任何其他类似的东西,因此这两个东西在语义上是等价的。当然,可以实现一个parallel版本的forEach(标准库中可能已经有),在这种情况下lambda表达式语法会非常有用。 - AardvarkSoup
9
forEach方法被调用的实例是一个Stream(http://lambdadoc.net/api/java/util/stream/Stream.html)。要请求并行执行,可以编写joins.parallel().forEach(...)。 - mschenk74
14
joins.forEach((join) -> mIrc.join(mSession, join)); 真的是将 for (String join : joins) { mIrc.join(mSession, join); } 简化了吗?你增加了标点符号的数量,从9个变成了12个,以隐藏 join 的类型。实际上,你只是把两个语句放到了一行上。 - Tom Hawtin - tackline
9
另一个需要考虑的问题是Java的有限变量捕获能力。使用Stream.forEach()时,由于局部变量被捕获后会变成final类型,因此无法更新它们,这意味着在forEach lambda中可能会出现具有状态的行为(除非你准备使用一些丑陋的方法,例如使用类状态变量)。 - RedGlyph
显示剩余6条评论
8个回答

635
更好的做法是使用for-each。除了违反“保持简单愚蠢”原则外,新式的forEach()至少存在以下缺陷:
  • 无法使用非最终变量。因此,像下面这样的代码无法转换为forEach lambda:
Object prev = null;
for(Object curr : list)
{
    if( prev != null )
        foo(prev, curr);
    prev = curr;
}

  • 无法处理已检查异常。Lambda 表达式并不禁止抛出已检查异常,但常见的函数式接口(如 Consumer)没有声明任何异常。因此,任何抛出已检查异常的代码都必须将其包装在 try-catchThrowables.propagate() 中。但即使你这样做了,抛出的异常的去向也不总是清晰的。它可能会在 forEach() 的内部某处被吞噬。

  • 流程控制有限。Lambda 中的 return 相当于 for-each 中的 continue,但没有与 break 相对应的等价物。同时,像返回值、短路或设置标志这样的操作也很困难(如果不违反“无非终态变量”规则,这将稍微缓解一些问题)。“这不仅仅是优化问题,而且在考虑到某些序列(例如读取文件中的行)可能具有副作用,或者你可能有一个无限序列时,这更是至关重要的。”

  • 可能会并行执行,这对于除了需要进行优化的 0.1% 的代码之外的所有代码来说都是可怕的事情。任何并行代码都必须经过深思熟虑(即使它不使用锁、易失性和传统多线程执行的其他特别恶劣的方面)。任何错误都将很难找到。

  • 可能会影响性能,因为 JIT 无法像普通循环一样对 forEach()+lambda 进行优化,尤其是现在 lambda 是新的。所谓“优化”并不是指调用 lambda 的开销(这很小),而是指现代 JIT 编译器对运行代码进行的复杂分析和转换。

  • 如果确实需要并行处理,则使用 ExecutorService 可能更快且不太困难。流既是自动的(即不知道你的问题),又使用了专门的(即针对一般情况效率低下的)并行化策略(fork-join 递归分解)。

  • 使调试更加混乱,因为存在嵌套的调用层次结构和并行执行。调试器可能无法显示周围代码中的变量,而像逐步执行之类的操作可能无法按预期工作。

  • 总的来说,流更难编写、阅读和调试。实际上,这适用于复杂的“流畅”API。复杂的单语句、大量使用泛型以及缺乏中间变量的组合会产生令人困惑的错误消息,并使调试变得繁琐。与其说是“这个方法没有 X 类型的重载”,不如说是“你在某个地方搞错了类型,但我们不知道是哪里或者怎么搞错的。”同样,当代码分解为多个语句并将中间值保存到变量中时,无法像在此情况下那样轻松地通过调试器步进并检查事物。最后,阅读代码并理解每个执行阶段的类型和行为可能是非常棘手的

    正如你所见,除非在有意义的情况下,否则我不是forEach()的忠实粉丝。

    尤其让我反感的是,Stream没有实现Iterable(尽管实际上有iterator方法),不能在for-each中使用,只能使用forEach()。我建议使用(Iterable<T>)stream::iterator将Stream转换为Iterable。更好的选择是使用StreamEx,它解决了一些Stream API的问题,包括实现Iterable。

    话虽如此,forEach()对以下情况很有用:

    • 原子性地迭代同步列表。在此之前,使用Collections.synchronizedList()生成的列表在诸如获取或设置等方面具有原子性,但在迭代时不是线程安全的。

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

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

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

    我参考的文章:

    编辑:看起来一些关于lambda的最初提案(例如http://www.javac.info/closures-v06a.html Google缓存)解决了我提到的一些问题(当然也增加了自己的复杂性)。


110
“为什么鼓励在表达式中隐藏副作用?”是错误的问题。函数式的forEach存在的目的是鼓励使用不带副作用的表达式来实现函数式编程。如果你发现forEach与你的副作用无法很好地配合,那么你应该感到你没有使用正确的工具来完成任务。简单的答案是,那是因为你的感觉是正确的,所以对于那种情况,请继续使用for-each循环。传统的for循环并没有被弃用... - Holger
22
forEach如何在没有副作用的情况下使用? - Aleksandr Dubinsky
18
好的,我表述不够准确,forEach 是唯一一个旨在进行副作用的流操作,但它并不是像你的示例代码那样的副作用,计数是典型的 reduce 操作。我建议,作为一个经验法则,将每个操作都保留在传统的 for 循环中,这些操作会操作本地变量或影响控制流程(包括异常处理)。关于原始问题,我认为问题源于有人使用了一个流,而简单的 for 循环可以覆盖流的源。只有在 forEach() 起作用的情况下才使用流。 - Holger
8
forEach适合用于哪些会产生副作用的示例? - Aleksandr Dubinsky
38
某些东西可以单独处理每个项目,而不尝试改变本地变量。例如,操作项目本身或将它们打印、写入/发送到文件、网络流等。如果您对这些示例有疑问并且没有看到任何应用程序,那对我来说也没有问题;过滤、映射、减少、搜索和(在较小程度上)收集是流的首选操作。对我来说,forEach看起来像是与现有API链接的便利方式。当然,对于并行操作也是如此。这些无法使用for循环工作。 - Holger
显示剩余19条评论

174

当操作可以并行执行时,这种优势就会发挥作用。(请参见http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and中关于内部和外部迭代的部分)

  • 我认为最主要的优点是,在循环内定义要执行的任务实现,而不必决定它是并行还是顺序执行。

  • 如果你想让你���循环并行执行,只需要写

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

你需要编写一些额外的代码来处理线程等内容。

注意:在我的回答中,我假设连接实现了java.util.Stream接口。如果连接仅实现了java.util.Iterable接口,则不再成立。


4
这份博客(https://blogs.oracle.com/darcy/resource/Devoxx/Devoxx2012_ProjectLambda.pdf)中Oracle工程师所引用的幻灯片并未提及lambda表达式中的并行性。这种并行性可能出现在像`map`和`fold`这样的批量集合方法中,而这些方法与lambda表达式并不真正相关。 - Thomas Jungblut
1
OP的代码似乎并不会从自动并行处理中受益(特别是因为没有保证一定会有)。我们并不真正知道“mIrc”是什么,但“join”似乎不是可以无序执行的东西。 - Eugene Loy
12
Stream#forEachIterable#forEach 不是同一件事情。OP 正在询问的是 Iterable#forEach - gvlasov
2
我使用了UPDATEX风格,因为在问题提出和回答更新之间规范发生了变化。如果没有回答的历史记录,我认为会更加混乱。 - mschenk74
1
请问有人能解释一下,如果joins实现了Iterable而不是Stream,为什么这个答案无效?从我读过的一些东西来看,如果joins实现了Iterable,那么OP应该能够执行joins.stream().forEach((join) -> mIrc.join(mSession, join));joins.parallelStream().forEach((join) -> mIrc.join(mSession, join)); - Blueriver
显示剩余4条评论

130
阅读这个问题时,人们可能会认为使用lambda表达式的Iterable#forEach是编写传统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) 循环体可以被不同实现的函数替换,而不影响周围的代码。无法访问外部范围的非final变量不是函数/lambda的缺陷,而是一个特性,它区分了Iterable#forEach的语义与传统的for-each循环的语义。一旦熟悉了Iterable#forEach的语法,它会使代码更易读,因为您立即获得了有关代码的额外信息。
传统的for-each循环在Java中肯定会保持良好的实践(避免过度使用术语“最佳实践”)。但这并不意味着Iterable#forEach应该被认为是不好的实践或不好的风格。使用正确的工具来完成工作始终是一个好习惯,这包括在适当的情况下混合传统的for-each循环和Iterable#forEach。

既然这个线程已经讨论了 Iterable#forEach 的缺点,那么下面是一些你可能想使用 Iterable#forEach 的原因:

  • To make your code more explicit: As described above, Iterable#forEach can make your code more explicit and readable in some situations.

  • To make your code more extensible and maintainable: Using a function as the body of a loop allows you to replace this function with different implementations (see Strategy Pattern). You could e.g. easily replace the lambda expression with a method call, that may be overwritten by sub-classes:

    joins.forEach(getJoinStrategy());
    

    Then you could provide default strategies using an enum, that implements the functional interface. This not only makes your code more extensible, it also increases maintainability because it decouples the loop implementation from the loop declaration.

  • To make your code more debuggable: Seperating the loop implementation from the declaration can also make debugging more easy, because you could have a specialized debug implementation, that prints out debug messages, without the need to clutter your main code with if(DEBUG)System.out.println(). The debug implementation could e.g. be a delegate, that decorates the actual function implementation.

  • To optimize performance-critical code: Contrary to some of the assertions in this thread, Iterable#forEach does already provide better performance than a traditional for-each loop, at least when using ArrayList and running Hotspot in "-client" mode. While this performance boost is small and negligible for most use cases, there are situations, where this extra performance can make a difference. E.g. library maintainers will certainly want to evaluate, if some of their existing loop implementations should be replaced with Iterable#forEach.

    To back this statement up with facts, I have done some micro-benchmarks with Caliper. Here is the test code (latest Caliper from git is needed):

    @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++;
                }
            }
        }
    }
    

    And here are the results:

    When running with "-client", Iterable#forEach outperforms the traditional for loop over an ArrayList, but is still slower than directly iterating over an array. When running with "-server", the performance of all approaches is about the same.

  • To provide optional support for parallel execution: It has already been said here, that the possibility to execute the functional interface of Iterable#forEach in parallel using streams, is certainly an important aspect. Since Collection#parallelStream() does not guarantee, that the loop is actually executed in parallel, one must consider this an optional feature. By iterating over your list with list.parallelStream().forEach(...);, you explicitly say: This loop supports parallel execution, but it does not depend on it. Again, this is a feature and not a deficit!

    By moving the decision for parallel execution away from your actual loop implementation, you allow optional optimization of your code, without affecting the code itself, which is a good thing. Also, if the default parallel stream implementation does not fit your needs, no one is preventing you from providing your own implementation. You could e.g. provide an optimized collection depending on the underlying operating system, on the size of the collection, on the number of cores, and on some preference settings:

    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();
            }
        }
    }
    

    The nice thing here is, that your loop implementation doesn't need to know or care about these details.


5
您在这次讨论中提出了一些有趣的观点,我会尝试回答它们。您建议根据循环体的性质在forEachfor-each之间切换。智慧和遵守这些规则的纪律是一名优秀程序员的标志。这些规则也是他的灾难,因为他周围的人要么不遵守它们,要么持不同意见,例如使用已检查的异常和未检查的异常。这种情况似乎更加微妙。但是,如果循环体“不影响周围的代码或流程控制”,将其分解为一个函数是否更好呢? - Aleksandr Dubinsky
5
感谢Aleksandr提供详细的评论。但是,如果这个循环体“不影响周围的代码或者流程控制”,将其拆分为一个函数是否更好呢?在我看来,这通常是个不错的选择 - 将这些循环体拆分为函数是一个自然而然的结果。 - Balder
2
关于性能问题 - 我想这很大程度上取决于循环的性质。在我正在工作中的一个项目中,我一直在使用类似于 Java 8 之前的 Iterable#forEach 的函数式循环,只是因为它可以提高性能。所涉及的项目具有一个主循环,类似于游戏循环,并且有数量不确定的嵌套子循环,在其中客户端可以将循环参与者作为函数连接起来。这样的软件结构非常适合使用 Iterable#forEach - Balder
8
在我的评论末尾有这样一句话:“代码应该用习语来表达,使用的习语越少,代码就越清晰,决定使用哪个习语的时间也就越少。”当我从C#转换到Java时,我开始深刻地理解这一点。 - Aleksandr Dubinsky
8
这是一个糟糕的论点。你可以用它来为任何你想要的东西辩护:比如说,为什么不应该使用for循环,因为while循环已经足够好了,这样就少了一个习惯用语。甚至可以说,为什么要使用任何循环、开关或try/catch语句,当goto可以完成所有这些操作并且更多。 - tapichu
显示剩余11条评论

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/以比较不同用例的内部/外部迭代。


9
可迭代对象为什么知道最佳的方式,而迭代器却不知道? - mschenk74
2
没有本质的区别,但需要额外的代码来符合迭代器接口,这可能会更加昂贵。 - ZhongYu
1
如果你实现了Collection,那么你也会实现Iterable。因此,在“添加更多代码以实现缺失接口方法”的方面,没有任何代码开销,如果这是你的观点的话。正如mschenk74所说,似乎没有理由不能调整迭代器以了解如何以最佳方式迭代你的集合。我确实同意迭代器创建可能会有开销,但是,说实话,这些东西通常非常便宜,你可以说它们几乎没有成本... - Eugene Loy
4
举个例子,迭代树的操作:void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);},这种方法比外部迭代更加优雅,同时你可以自行决定如何最佳同步。 - ratchet freak
@leo 这些集合的作者非常关注性能。 - ZhongYu
1
有趣的是,在String.join方法中唯一的注释(好吧,join错误了)是“元素数量不太值得使用Arrays.stream开销”,所以他们使用了一个高级的for循环。 - Tom Hawtin - tackline

10

简而言之: List.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)。 在主类中,我运行测试方法三次以预热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);

我在一台i5四核CPU上运行了这个程序,使用的是Java版本1.8.0_05。

传统for循环

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

执行时间:4.21毫秒

传统的foreach循环

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

execution time: 5.95 ms

List.forEach()

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

execution time: 3.11 ms

List.stream().forEach()

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

执行时间:2.79毫秒

List.parallelStream().forEach

(列表的并行流程,对于每个元素执行操作)
list.parallelStream().forEach((i) -> doIt(i));

执行时间:3.6毫秒


26
你是如何获得这些数字的?你使用哪个基准测试框架?如果你没有使用任何基准测试框架,而只是简单地使用“System.out.println”来朴素地显示这些数据,那么所有的结果都是无用的。 - Luiggi Mendoza
2
没有框架。我使用 System.nanoTime()。如果你看了答案,你就会知道它是如何完成的。我认为这并不使它无用,因为这是一个相对的问题。我不关心某种方法做得有多好,我关心它与其他方法相比做得有多好。 - Assaf
35
一个好的微基准测试的目的就是如此。由于您没有满足这些要求,因此结果是无用的。 - Luiggi Mendoza
7
我建议你了解一下JMH,这是Java官方使用的工具,它会花费很多精力确保测试结果准确无误。 链接:http://openjdk.java.net/projects/code-tools/jmh/ - dsvensson
1
叹气。在任何理智的编译器中,经典的for循环应该比除了(但通常不是)并行循环之外的任何东西都要快得多。这告诉我JVM在内联方面比循环展开更好。 - hoodaticus
显示剩余2条评论

9

我感觉需要稍微扩展一下我的评论...

关于范式\风格

这可能是最显著的方面。FP之所以流行,是因为你可以避免副作用而得到的好处。我不会深入探讨从中可以得到什么优缺点,因为这与问题无关。

然而,我会说使用Iterable.forEach进行迭代是受到FP启发的,而且更多地将FP带到了Java中(具有讽刺意味的是,我会说在纯FP中没有太多使用forEach的用途,因为它除了引入副作用外什么都不做)。

最后,我想说这更多是你当前编写的口味、风格和范式的问题。

关于并行性。

从性能角度来看,使用Iterable.forEach比foreach(...)没有明显的好处。

根据官方Iterable.forEach文档

对Iterable的内容执行给定的操作,在迭代时元素出现的顺序中,直到所有元素都已被处理或操作抛出异常。

...即文档非常清楚,没有隐含的并行性。添加一个将是LSP违规。

现在,Java 8中有“平行集合”承诺,但要使用它们,您需要更明确并注意使用它们(例如,请参见mschenk74的答案)。

顺便说一下:在这种情况下,将使用Stream.forEach,它不能保证实际工作将以并行方式完成(取决于底层集合)。

更新:可能不那么明显,一眼看去有点牵强,但从样式和可读性角度来看,还有另一个方面。

首先-普通的for循环是平淡无奇的。每个人都已经知道了它们。

其次,更重要的是-您可能只想使用具有一行代码的lambda表达式进行Iterable.forEach。如果“body”变得更重-它们往往不太可读。 从这里开始,您有两个选择-使用内部类(yuck)或使用普通的forloop。 当人们看到同一代码库中的相同事物(对集合进行迭代)以各种不同的方式/风格执行时,他们经常感到烦恼,这似乎就是这种情况。

同样,这可能或可能不是一个问题。取决于处理代码的人们。


1
并行处理不需要新的“并行集合”。它只取决于您是否请求顺序流(使用collection.stream())或并行流(使用collection.parallelStream())。 - JB Nizet
根据文档,Collection.parallelStream()不能保证实现集合将返回并行流。我自己也在想,这可能取决于集合本身。 - Eugene Loy
@JBNizet,我同意你的观点,但这并不是我在一开始所说的“并行集合”的真正含义。我提到的是Java 8中新增的Collection.parallelStream(),通过与Scala的概念类比,称之为“并行集合”。此外,我不确定JSR中它被称为什么,但我看到有几篇论文使用了相同的术语来描述这个Java 8功能。 - Eugene Loy
1
对于最后一段,您可以使用函数引用:collection.forEach(MyClass::loopBody); - ratchet freak
在英语中,斜杠(“/”)代替反斜杠(“\”)。 - Aleksandr Dubinsky
显示剩余2条评论

6

最不受欢迎的函数之一是forEach,它的一个限制是缺少对检查异常的支持。

可能的解决方法之一是使用普通的for循环来替换终端的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表达式中强制使用检查异常处理。为什么是强制性的而不是可选的?(链接)

2
Java 1.8的forEach方法相比于1.7的增强for循环的优势在于编写代码时可以只关注业务逻辑。
forEach方法将java.util.function.Consumer对象作为参数,因此它有助于将我们的业务逻辑放在一个单独的位置,随时可以重用。
请看下面的片段:
  • Here I have created new Class that will override accept class method from Consumer Class, where you can add additional functionility, More than 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);
                }
            });
        }
    }
    

这也可以使用for循环实现。 - Nicola Musatti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接