编写Java代码时,NetBeans经常鼓励我将foreach循环(使用迭代器)转换为lambda表达式。有时候生成的代码更清晰。其他时候,结果不如以前那么清晰。
例如,以下使用迭代器:
List<String> list = new ArrayList<>();
for (String str : list) {
if (str.charAt(0) == ' ')) {
// do something with str
}
}
等效使用lambda表达式:
List<String> list = new ArrayList<>();
list.stream().filter((str) -> (str.charAt(0) == ' ')).forEach((str) -> {
// do something with str
)};
在这种情况下,使用lambda表达式会导致代码更长,并使用不太直观的语言(stream
,filter
和forEach
而不是for
和{{1 }})。因此,当代码不清晰时,使用lambdas而不是迭代器有什么好处吗?例如,是否有任何性能提升?
答案 0 :(得分:3)
迭代收集并对其中某些成员执行“某些操作”并不是使用lambdas的最佳示例。
考虑如下场景,比如创建由某些属性过滤的对象的结果集合,按其他属性排序并执行其他一些“魔术”,您将看到lambdas可以节省数十行代码。
很难说它是否更容易阅读(可能不像lambda是你必须熟悉的另一种语法)但毕竟 - 最好是阅读一行复杂的代码而不是创建匿名/内部比较器。至少在C#中lambda非常有用。
答案 1 :(得分:2)
使用不太直观的语言(
stream
,filter
和forEach
代替for
和if
我真的觉得它们不那么直观。而且,等待几个月,这些将主要用于您将在Java中听到的术语。事实上,一旦你对lambdas感到满意,你会发现它清理你的代码是多么令人惊奇,它在循环中使用了复杂的逻辑。
当代码不干净时,使用lambdas而不是迭代器有什么好处吗?例如,是否有任何性能提升?
我想到的流和lambdas的一个明显优势是,它使用Stream.parallelStream()
更轻松地为您提供并行执行的强大功能。此外,流的内部迭代可控制API发生迭代的方式。它可以选择懒惰,平行,顺序等评估中间操作。此外,函数式编程有其自身的优势。您可以以lambdas的形式传递逻辑,以前使用匿名类完成。这样,一些功能可以很容易地重复使用。
虽然与普通for
循环相比也存在某些缺点。如果您的循环正在修改某个局部变量,那么您无法将其转换为forEach
和lambdas版本(至少不是直接),因为lambdas中使用的变量需要有效final
更多细节可以在java.util.stream
javadoc找到。
答案 2 :(得分:2)
只是为了给你一些关于我认为你能写出整齐的lambdas的信息,这是给你的代码:
List<String> listString = new ArrayList<>();
for (String str : listString) {
if (str.charAt(0) == ' ') {
// do something with str
}
}
我愿意,将其转换为以下内容:
List<String> listString = new ArrayList<>();
listString.stream()
.filter(s -> s.charAt(0) == ' ')
.forEach(/*do something*/);
我觉得这样的语法不那么具有干扰性和清晰度。另外,如果你在lambda中需要块,你可能做错了。除非你有充分的理由这样做。
例如,如果您想要在新行上打印每个字符串,您可以这样做:
List<String> listString = new ArrayList<>();
listString.stream()
.filter(s -> s.charAt(0) == ' ')
.forEach(System.out::println);
此外,如果您需要以某种结构存储数据,您希望使用Collectors.*
,并且您不希望在forEach
内执行此操作,这是一个愚蠢的例子我们想将其再次转换为List<String>
:
List<String> listString = new ArrayList<>();
List<String> filteredString = listString.stream()
.filter(s -> s.charAt(0) == ' ')
.collect(Collectors.toList());
请注意,如果允许您修改原始列表,则可以更轻松地完成此特定实现。
答案 3 :(得分:1)
关于性能提升,我已经为lambda表达式与迭代器编写了一个简单的基准测试。该测试在预热阶段和非预热阶段提供性能。前10次迭代考虑预热阶段,后续非预热阶段。测试运行10k次迭代并测量平均时间。以下是代码:
import java.util.ArrayList;
import java.util.List;
public class LambdaTest {
private static final int COUNT = 10000;
public static void main(String[] args) {
List<String> str = new ArrayList<String>();
for (int i =0; i<100000; i++){
str.add(""+i);
}
double iterTotal = 0;
double lambdaTotal = 0;
double warmupIterTotal = 0;
double warmupLambdaTotal = 0;
for(int j = 0; j < COUNT; j++){
long start = System.nanoTime();
for (int i = 0; i< 100000; i++){
String string = str.get(i);
// if(string.length() < 5){
// System.out.println(string);
// }
}
long end = System.nanoTime();
if(j>=10){
iterTotal += end - start;
}else {
warmupIterTotal += end - start;
}
System.out.println("Output 1 in : "+(end-start)*1.0/1000 +" Us");
start = System.nanoTime();
str.forEach((string) -> {
// if(string.length() < 5){
// System.out.println(string);
// }
});
end = System.nanoTime();
if(j>=10){
lambdaTotal+= end-start;
}else {
warmupLambdaTotal += end - start;
}
System.out.println("Output 2 in : "+(end-start)*1.0/1000 +" Us");
}
System.out.println("Avg Us during warmup using Iter: "+warmupIterTotal/(1000*10));
System.out.println("Avg Us during warmup using Lambda: "+warmupLambdaTotal/(1000*10));
System.out.println("Avg Us using Iter: "+iterTotal/(1000*(COUNT-10)));
System.out.println("Avg Us using Lambda: "+lambdaTotal/(1000*(COUNT-10)));
}
}
以上代码的输出如下:
使用Iter:1372.8821的热身期间平均我们
在使用Lambda进行热身时平均我们:5211.7064
使用Iter平均值:373.6436173173173
使用Lambda平均值:370.77465015015014
因此,正如我们所看到的,lambda表达式在预热阶段表现不佳,但是后期预热,使用lambda表达式的性能与迭代器非常相似。这只是一个简单的测试,如果您在应用程序中尝试复杂的表达式,则可以更好地分析应用程序,并检查哪个应用程序在您的情况下效果更好。