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


当前回答

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

关于范式\风格

这可能是最值得注意的方面。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

更好的做法是使用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)解决了我提到的一些问题(当然,同时增加了它们自己的复杂性)。

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

当操作可以并行执行时,优势就显现出来了。(参见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/风格时,他们经常会感到恼火,这似乎是事实。

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