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


当前回答

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

其他回答

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

关于范式\风格

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

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

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

并行性。

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

根据Iterable的官方文档。forEach:

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

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

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

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

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

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

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

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

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

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

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