以下哪一项在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返回或打破父函数,这也应该在比较它们时考虑到,但还有其他要考虑的吗?


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

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

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


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

关于范式\风格

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

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

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

并行性。

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

根据Iterable的官方文档。forEach:

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

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

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

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

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

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

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

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


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


更好的做法是使用for-each。除了违反Keep It Simple, Stupid原则外,新发明的forEach()至少有以下不足:

不能使用非最终变量。因此,像下面这样的代码不能转换为forEach lambda:

对象prev = null; for(对象curr: list) { If (prev != null) foo(上一页,咕咕叫); Prev = curr; }

Can't handle checked exceptions. Lambdas aren't actually forbidden from throwing checked exceptions, but common functional interfaces like Consumer don't declare any. Therefore, any code that throws checked exceptions must wrap them in try-catch or Throwables.propagate(). But even if you do that, it's not always clear what happens to the thrown exception. It could get swallowed somewhere in the guts of forEach() Limited flow-control. A return in a lambda equals a continue in a for-each, but there is no equivalent to a break. It's also difficult to do things like return values, short circuit, or set flags (which would have alleviated things a bit, if it wasn't a violation of the no non-final variables rule). "This is not just an optimization, but critical when you consider that some sequences (like reading the lines in a file) may have side-effects, or you may have an infinite sequence." Might execute in parallel, which is a horrible, horrible thing for all but the 0.1% of your code that needs to be optimized. Any parallel code has to be thought through (even if it doesn't use locks, volatiles, and other particularly nasty aspects of traditional multi-threaded execution). Any bug will be tough to find. Might hurt performance, because the JIT can't optimize forEach()+lambda to the same extent as plain loops, especially now that lambdas are new. By "optimization" I do not mean the overhead of calling lambdas (which is small), but to the sophisticated analysis and transformation that the modern JIT compiler performs on running code. If you do need parallelism, it is probably much faster and not much more difficult to use an ExecutorService. Streams are both automagical (read: don't know much about your problem) and use a specialized (read: inefficient for the general case) parallelization strategy (fork-join recursive decomposition). Makes debugging more confusing, because of the nested call hierarchy and, god forbid, parallel execution. The debugger may have issues displaying variables from the surrounding code, and things like step-through may not work as expected. Streams in general are more difficult to code, read, and debug. Actually, this is true of complex "fluent" APIs in general. The combination of complex single statements, heavy use of generics, and lack of intermediate variables conspire to produce confusing error messages and frustrate debugging. Instead of "this method doesn't have an overload for type X" you get an error message closer to "somewhere you messed up the types, but we don't know where or how." Similarly, you can't step through and examine things in a debugger as easily as when the code is broken into multiple statements, and intermediate values are saved to variables. Finally, reading the code and understanding the types and behavior at each stage of execution may be non-trivial. Sticks out like a sore thumb. The Java language already has the for-each statement. Why replace it with a function call? Why encourage hiding side-effects somewhere in expressions? Why encourage unwieldy one-liners? Mixing regular for-each and new forEach willy-nilly is bad style. Code should speak in idioms (patterns that are quick to comprehend due to their repetition), and the fewer idioms are used the clearer the code is and less time is spent deciding which idiom to use (a big time-drain for perfectionists like myself!).

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

对我来说特别讨厌的是Stream没有实现Iterable(尽管实际上有方法迭代器),并且不能在for-each中使用,只能在forEach()中使用。我建议使用(Iterable<T>)stream::iterator将Streams转换为Iterables。更好的选择是使用StreamEx,它修复了许多流API问题,包括实现Iterable。

也就是说,forEach()在以下方面是有用的:

Atomically iterating over a synchronized list. Prior to this, a list generated with Collections.synchronizedList() was atomic with respect to things like get or set, but was not thread-safe when iterating. Parallel execution (using an appropriate parallel stream). This saves you a few lines of code vs using an ExecutorService, if your problem matches the performance assumptions built into Streams and Spliterators. Specific containers which, like the synchronized list, benefit from being in control of iteration (although this is largely theoretical unless people can bring up more examples) Calling a single function more cleanly by using forEach() and a method reference argument (ie, list.forEach (obj::someMethod)). However, keep in mind the points on checked exceptions, more difficult debugging, and reducing the number of idioms you use when writing code.

我参考的文章:

关于Java 8的一切 由内而外的迭代(正如另一张海报所指出的)

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


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


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


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表达式中强制检查异常处理。为什么是强制性的,而不是选择性的?


Java 1.8 forEach方法相对于1.7 Enhanced 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); } }); } }