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

Java 8:

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

Java 7:

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

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

EDIT

我还将把这个问题扩展到更长的方法。我知道你不能从lambda返回或打破父函数,这也应该在比较它们时考虑到,但还有其他要考虑的吗?


当前回答

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

以下是lambdas和流中检查异常处理的其他变通方法中最常见的问题:

Java 8 Lambda函数抛出异常?

Java 8: Lambda-Streams,过滤方法与异常

我如何从Java 8流内部抛出CHECKED异常?

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

其他回答

forEach()可以实现得比for-each循环快,因为迭代对象知道迭代其元素的最佳方式,而不是标准迭代器方式。所以区别在于内部循环还是外部循环。

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

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

与for-each循环相反,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/,以比较不同用例的内部/外部迭代。

我觉得我有必要扩展一下我的评论……

关于范式\风格

这可能是最值得注意的方面。FP之所以流行是因为你可以避免副作用。我不会深入研究你能从中得到什么优点\缺点,因为这与问题无关。

不过,我要说的是使用Iterable的迭代。forEach的灵感来自FP,是将更多的FP引入Java的结果(具有讽刺意味的是,我要说forEach在纯FP中没有多大用处,因为它除了引入副作用之外什么都不做)。

最后,我想说的是,这是你目前写作的品味、风格和范式的问题。

并行性。

从性能的角度来看,使用Iterable并没有明显的好处。一个接一个(…)。

根据Iterable的官方文档。forEach:

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

... 例如,文档非常清楚,将没有隐式并行。增加一个将违反LSP。

现在,Java 8中承诺了“并行集合”,但要使用这些集合,你需要更加明确,并在使用它们时多加注意(例如,请参阅mschenk74的回答)。

顺便说一句:在这种情况下,流。forEach将被使用,并且它不保证实际工作将并行完成(取决于底层集合)。

更新:可能不是那么明显和有点拉伸一眼,但有另一个方面的风格和可读性的观点。

首先,简单的forloop是简单而古老的。大家都已经知道了。

第二,也是更重要的——你可能想要使用Iterable。forEach只使用一行程序lambdas。如果“身体”变重了,它们就不那么可读了。 从这里你有两个选择-使用内部类(讨厌)或使用普通的旧forloop。 当人们看到相同的事情(迭代集合)在相同的代码库中进行不同的vays/风格时,他们经常会感到恼火,这似乎是事实。

同样,这可能是问题,也可能不是问题。这取决于编写代码的人。

TL;DR: List.stream(). foreach()是最快的。

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

经典的 经典的foreach List.forEach () .forEach List.stream () () .forEach List.parallelStream ()

测试程序和参数

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

我在i5 4核CPU上运行这个程序,java版本为1.8.0_05

经典的

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

执行时间:4.21 ms

经典的foreach

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

执行时间:5.95毫秒

List.forEach ()

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

执行时间:3.11 ms

.forEach List.stream () ()

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

执行时间:2.79 ms

.forEach List.parallelStream ()

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

执行时间:3.6 ms

当操作可以并行执行时,优势就显现出来了。(参见http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and -关于内部和外部迭代的部分)

从我的观点来看,主要的优点是可以定义在循环中要做的事情的实现,而不必决定它是并行执行还是顺序执行 如果你想让你的循环并行执行,你可以简单地写 joins.parallelStream()。forEach(join -> mIrc。加入(mSession加入)); 你将不得不为线程处理等编写一些额外的代码。

注意:对于我的回答,我假设连接实现java.util.Stream接口。如果联接只实现java.util.Iterable接口,则不再是这样。

当阅读这个问题时,你可能会有这样的印象,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);
}

Replacing the body of a loop with a functional interface, as in the examples above, makes your code more explicit: You are saying that (1) the body of the loop does not affect the surrounding code and control flow, and (2) the body of the loop may be replaced with a different implementation of the function, without affecting the surrounding code. Not being able to access non final variables of the outer scope is not a deficit of functions/lambdas, it is a feature that distinguishes the semantics of Iterable#forEach from the semantics of a traditional for-each loop. Once one gets used to the syntax of Iterable#forEach, it makes the code more readable, because you immediately get this additional information about the code.

传统的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: Results for -client Results for -server 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.