在Lambda或简单循环方面哪个更好?

时间:2015-07-28 11:43:31

标签: java lambda java-8

我已经快速阅读了Oracle Lambda Expression文档。

这种例子让我更好地理解:

//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));


//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

尽管如此,我还是不明白为什么会有这样的创新。它只是一种方法,当&#34;方法变量&#34;结束,对吗?为什么我应该使用它而不是真正的方法? 在性能方面哪个是更好的选择。 Lambda或简单循环。

9 个答案:

答案 0 :(得分:45)

我的建议是:

  1. 使用您和您的同事同意最易维护的风格。

  2. 如果您和您的同事还不熟悉lambda,请继续学习。

  3. 不要过分痴迷于表现。这通常不是最重要的事情。

  4. 一般来说,lambdas和stream提供了更简洁的表达方式(一旦每个人都达到速度),表达这种算法的可读性更强。绩效不是主要目标。

    如果性能确实成为问题,那么标准建议就是编码,测试,分析和优化。您可以通过在编码阶段进行优化来轻松浪费时间。让分析器指出代码的哪些部分值得优化。

    在这个具体的例子中,性能差异太小而无法衡量。如果你扩展到数百万个元素的列表,性能将由构建列表和写入数字所花费的时间占主导地位。不同的迭代方式只会对整体绩效产生很小的影响。

    对于那些人(尽管如上所述)想要知道使用lambda或传统循环是否更快,最好的一般答案是:

      

    “这取决于各种因素1)未被充分理解,2)随着Java编译器技术的发展而易于改变。

    我们可以为特定Java主要/次要/修补程序版本的特定示例提供答案,但概括是不明智的。

答案 1 :(得分:10)

  

为什么我应该使用它而不是真正的方法?

你不应该。使用您喜欢的方法。

至于性能,我想,所有这些版本大致同样快。这里I / O操作(println)比调用lambda或创建迭代器的所有可能开销要慢得多。一般来说,forEach可能稍快一些,因为它会在单个方法中执行所有内容而不创建Iterator并调用hasNextnext(这是由for-each循环隐式完成的) )。无论如何,这取决于许多因素,例如您调用此代码的频率,列表的长度,JIT编译器是否设法对迭代器和/或lambda进行虚拟化等等。

答案 2 :(得分:6)

它允许你在一行中写入(虽然具有足够的可读性),这是以前不可能的。此处的效果不存在问题O(n)逗留O(n)

例如,在我的Windows Phone应用程序中,我有会话,并且会话中有表演者,我想选择所有具有一个具体表演者的会话(就像你想看到一些演员所播放的所有电影)。在Java 1.7或更低版​​本中,我必须使用内部循环创建循环,检查它,如果没有执行者等则返回null。 使用lambda表达式,我可以这样做:

//performerId is parameter passed by method before
Sessions.Where(x => x.Performers.Where(y => y.PerformerId == performerId).FirstOrDefault() != null)

现在在Java中也是一样(但是我现在没有在1.8项目上工作,我没有Java示例,但我很期待它。)

答案 3 :(得分:2)

就性能而言,与lambda相比,正常函数会更好,因为在groovy中存在闭包,它与lambda相同或更相似。

这些事情的工作方式就像你为任何集合编写一个闭包一样,它将在内部创建另一个实际上为所提到的闭包或lambda表达式做出操作的类。

但是,通过使用lambda和闭包,我可以更好地迭代事物以及我可以轻松调试。您可以编写更少的代码行。

答案 4 :(得分:2)

如果你想了解lambda表达式的值,你不应该看一下只有之前存在的语言对应的新方法。这些例子怎么样:

button.addActionListener( ev -> BACKGROUND_EXECUTOR_SERVICE.execute( () -> {
   String result = longComputation();
   SwingUtilities.invokeLater( () -> label.setText(result) );
});

只要考虑一下等效的Java 8之前的代码是什么样的,你会发现主要的优势不在于这里的性能。

如果您想查看收集操作,那么这个怎么样?

map.merge(key, 1, Integer::sum);

如果地图中尚未存在该键,则会将1放入地图中,否则将1添加到现有地图的值中。再一次,想想旧对手的样子。如果地图具有适当的方法实现,它可能更有效,因为它避免了多个哈希操作,这只是一个额外的好处。

当然,forEach方法不能提供如此大的表现力,因为每种语言都是对应的。但是,如果声明需要重复长类型名称和通用参数,则不需要声明循环变量可以改进源代码。在Map s:

的背景下尤其如此
Map<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> map;
//…

map.forEach((p,l)->l.forEach(i->{ /* do your work using p and i */}));

这里,这个新的迭代明显胜过

for(Map.Entry<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> e:
                                                                      map.entrySet()) {
    ContainerOrderFocusTraversalPolicy p=e.getKey();
    for(AttributedCharacterIterator i: e.getValue()) {
        /* do your work using p and i */
    }
}

当然,只有实际的工作语句足够小才重要,但这就是lambda表达式的使用方式:封装一小段代码。还有一些任务无法通过这种方式完成,并且需要一个普通的for-each循环,就像还有一些for-each循环无法完成的任务一样,需要更老的{{1} } loop循环处理for ...

答案 5 :(得分:1)

在代码中使用lambda时,你会告诉我该怎么做。上面的代码传递方法引用而不是循环遍历列表,我认为lambda更符合人体工程学。

就性能而言,打印100万件物品的时间几乎相同,但我还没有标记其他类型的操作。

上面的代码是一个简单的操作,但lambda有很多优点,因为你可以使用多个核心(并行流)等功能,你可以使用它们。

答案 6 :(得分:1)

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Timeout;
import org.openjdk.jmh.annotations.Warmup;

/**
 *
 * @author devb
 */
@BenchmarkMode(Mode.Throughput)
@Fork(value = 1)
@Warmup(iterations = 1, time = 32, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 16, time = 1, timeUnit = TimeUnit.SECONDS)
@Timeout(time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class CheckLamdaPerformance {

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

    @Benchmark
    public void testPerOld() {
        //Old way:
        for (Integer n : list) {
            System.out.println(n);
        }
    }

    @Benchmark
    public void testPerNew() {
        //New way:
        list.forEach(n -> System.out.println(n));
    }

    @Benchmark
    public void testPerDoubleColon() {
        //or we can use :: double colon operator in Java 8
        list.forEach(System.out::println);
    }

}

测试基准

import java.io.IOException;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.runner.RunnerException;

/**
 *
 * @author devb
 */
public class MyBenchmark {

    private static final String TEST = ".*CheckLamdaPerformance.*"; 

    public static void main(String[] args) throws IOException, RunnerException {
        Main.main(getArguments(TEST, 1, 5000, 1));
    }

    private static String[] getArguments(String className, int nRuns, int runForMilliseconds, int nThreads) {
        return new String[]{className,
            "-i", "" + nRuns,
            "-r", runForMilliseconds + "ms",
            "-t", "" + nThreads,
            "-w", "5000ms",
            "-wi", "1"
        };
    }

}

运行输出后:

# Run complete. Total time: 00:00:34

Benchmark                                  Mode  Cnt     Score   Error  Units
CheckLamdaPerformance.testPerDoubleColon  thrpt        776.008          ops/s
CheckLamdaPerformance.testPerNew          thrpt       1096.423          ops/s
CheckLamdaPerformance.testPerOld          thrpt        873.542          ops/s

答案 7 :(得分:0)

查看Guava documentation中的第一部分。它是关于较旧的Java版本,但重要的是 - 在任何地方使用lambdas实际上可能会使代码的可读性降低。更好的例子可能来自Stream API:

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

答案 8 :(得分:0)

运行时和可读性当然看起来有所不同,但在我看来有一个很好的答案

  

为什么我应该使用它而不是真正的方法?

您可以将lambda函数作为变量传递
如果我基于此,请纠正我,因为我现在已经成为Fantom用户一段时间了,但是您可以将Lambda函数作为参数传递给方法调用。例如,如果你构建了一个sort方法,它可以使用lambda参数作为比较器来使用,允许你使用单一排序方法来比较对象中的不同字段,甚至是完全不同的对象。