neb*_*kat 436 java for-loop java-8 java-stream
在Java 8中,以下哪项更好?
Java 8:
joins.forEach(join -> mIrc.join(mSession, join));
Run Code Online (Sandbox Code Playgroud)
Java 7:
for (String join : joins) {
mIrc.join(mSession, join);
}
Run Code Online (Sandbox Code Playgroud)
我有很多可以用lambdas"简化"的for循环,但使用它们真的有什么好处吗?它会改善他们的性能和可读性吗?
编辑
我还将这个问题扩展到更长的方法.我知道你不能从lambda中返回或破坏父函数,在比较它们时也应该考虑到这一点,但还有什么需要考虑的吗?
Ale*_*sky 477
更好的做法是使用for-each.除了违反Keep It Simple,Stupid原则外,新手forEach()还至少存在以下不足之处:
不能使用非最终变量.所以,像下面的代码不能变成forEach lambda:
Run Code Online (Sandbox Code Playgroud)Object prev = null; for(Object curr : list) { if( prev != null ) foo(prev, curr); prev = curr; }
无法处理已检查的异常.实际上并没有禁止Lambdas抛出已检查的异常,但是常见的功能接口Consumer不会声明任何异常.因此,任何抛出已检查异常的代码都必须将它们包装在try-catch或中Throwables.propagate().但即使你这样做,也不总是清楚抛出的异常会发生什么.它可能会吞噬到内心的某个地方forEach()
有限的流量控制.一个return在拉姆达等于continue在换每个,但没有相当于一个break.执行诸如返回值,短路或设置标志之类的事情也是很困难的(如果它不违反no non-final变量规则,这会稍微减轻一些事情)."这不仅仅是一种优化,但是当您考虑某些序列(如读取文件中的行)可能会产生副作用,或者您可能有无限序列时,这一点至关重要."
可能并行执行,这对于除了需要优化的0.1%代码之外的所有人来说都是一个可怕的,可怕的事情.必须考虑任何并行代码(即使它不使用锁,挥发性和传统多线程执行的其他特别令人讨厌的方面).任何错误都很难找到.
可能会损害性能,因为JIT无法将forEach()+ lambda优化为与plain循环相同的程度,特别是现在lambda是新的.通过"优化",我并不是指调用lambdas(这很小)的开销,而是指现代JIT编译器在运行代码时执行的复杂分析和转换.
如果确实需要并行性,那么使用ExecutorService可能会更快,也就不那么困难.流是自动的(阅读:对您的问题不太了解)并使用专门的(读取:一般情况下效率低下)并行化策略(fork-join递归分解).
由于嵌套的调用层次结构和上帝禁止的并行执行,使调试更加混乱.调试器可能在显示来自周围代码的变量时出现问题,并且诸如步进之类的事情可能无法按预期工作.
通常,流编码,读取和调试更加困难.实际上,一般而言,复杂的" 流畅 "API 也是如此.复杂的单个语句,大量使用泛型和缺少中间变量的组合共同产生令人困惑的错误消息并阻碍调试.而不是"此方法没有X类型的重载",你得到的错误消息更接近"你搞砸了类型,但我们不知道在哪里或如何." 同样,您不能像在代码被分解为多个语句时那样轻松地在调试器中单步执行并检查事物,并将中间值保存到变量中.最后,阅读代码并理解每个执行阶段的类型和行为可能并非易事.
像拇指一样疼痛.Java语言已经有for-each语句.为什么用函数调用替换它?为什么鼓励在表达式的某处隐藏副作用?为什么鼓励笨重的单行?定期为每一个和每个新的forWach willy-nilly混合是不好的风格.代码应该用成语说话(由于重复而易于理解的模式),使用的习语越少,代码越清晰,决定使用哪种成语的时间就越少(对于像我这样的完美主义者来说,这是一个很大的时间流逝! ).
正如你所看到的,我不是forEach()的忠实粉丝,除非它有意义.
对我来说特别令人反感的是这样的事实:Stream没有实现Iterable(尽管实际上有方法iterator)并且不能在for-each中使用,只能使用forEach().我建议将Streams转换为Iterables (Iterable<T>)stream::iterator.更好的选择是使用StreamEx来修复许多Stream API问题,包括实现Iterable.
也就是说,forEach()对以下内容有用:
以原子方式迭代同步列表.在此之前,生成的列表Collections.synchronizedList()对于诸如get或set之类的东西是原子的,但在迭代时不是线程安全的.
并行执行(使用适当的并行流).如果您的问题与Streams和Spliterators中内置的性能假设相匹配,那么与使用ExecutorService相比,这可以节省几行代码.
特定容器,与同步列表一样,受益于控制迭代(尽管这在很大程度上是理论上的,除非人们可以提出更多示例)
通过使用forEach()和方法引用参数(即,list.forEach (obj::someMethod))更干净地调用单个函数.但是,请记住检查异常的要点,更难调试,以及减少编写代码时使用的习语数量.
我用来参考的文章:
编辑:看起来一些lambda的原始提议(如http://www.javac.info/closures-v06a.html)解决了我提到的一些问题(当然,添加自己的复杂性).
msc*_*k74 169
当操作可以并行执行时,会考虑优势.(请参阅http://java.dzone.com/articles/devoxx-2012-java-8-lambda-以及 - 有关内部和外部迭代的部分)
从我的观点来看,主要优点是可以定义循环内要完成的内容的实现,而不必决定它是否将并行执行或顺序执行
如果你想让你的循环并行执行,你可以简单地写
joins.parallelStream().forEach(join -> mIrc.join(mSession, join));
Run Code Online (Sandbox Code Playgroud)
您将不得不为线程处理等编写一些额外的代码.
注意:对于我的回答,我假设连接实现了java.util.Stream接口.如果连接仅实现java.util.Iterable接口,则不再是这样.
Bal*_*der 104
在阅读这个问题时,可以得到印象,Iterable#forEach结合lambda表达式是编写传统for-each循环的快捷方式/替代.这是不正确的.来自OP的代码:
joins.forEach(join -> mIrc.join(mSession, join));
Run Code Online (Sandbox Code Playgroud)
是不是打算作为写作的捷径
for (String join : joins) {
mIrc.join(mSession, join);
}
Run Code Online (Sandbox Code Playgroud)
并且当然不应该以这种方式使用.相反,它旨在作为写作的快捷方式(尽管它不完全相同)
joins.forEach(new Consumer<T>() {
@Override
public void accept(T join) {
mIrc.join(mSession, join);
}
});
Run Code Online (Sandbox Code Playgroud)
它是以下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);
}
Run Code Online (Sandbox Code Playgroud)
用上面的例子替换一个带有功能接口的循环体,使你的代码更加明确:你说的是(1)循环体不影响周围的代码和控制流,(2)循环体可以用函数的不同实现替换,而不会影响周围的代码.不能够访问外部范围的非最终变量不是功能/ lambda表达式的逆差,这是一个特征区分的语义Iterable#forEach从传统的for-each循环的语义.一旦习惯了语法Iterable#forEach,它就会使代码更具可读性,因为您可以立即获得有关代码的其他信息.
传统的for-each循环肯定会在Java中保持良好的实践(避免过度使用的术语" 最佳实践 ").但这并不意味着,Iterable#forEach应该被视为不良做法或不良风格.使用正确的工具来完成工作总是很好的做法,这包括将传统的for-loop循环与Iterable#forEach有意义的混合.
由于Iterable#forEach已经在这个线程中讨论了缺点,这里有一些原因,为什么你可能想要使用Iterable#forEach:
使代码更加明确:如上所述,Iterable#forEach 可以使代码在某些情况下更加明确和可读.
使代码更具可扩展性和可维护性:使用函数作为循环体可以使用不同的实现替换此函数(请参阅策略模式).您可以使用方法调用轻松替换lambda表达式,这可能会被子类覆盖:
joins.forEach(getJoinStrategy());
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用实现功能接口的枚举来提供默认策略.这不仅使您的代码更具可扩展性,还增加了可维护性,因为它将循环实现与循环声明分离.
为了使代码更易于调试:从声明中分离循环实现也可以使调试更容易,因为您可以使用专门的调试实现,打印出调试消息,而不需要使用主代码混乱if(DEBUG)System.out.println().调试实现可以是例如委托,其装饰实际的功能实现.
为了优化性能关键代码:相反,一些在这个线程的断言,Iterable#forEach 并已经提供了比更好的性能传统的for-each循环,在使用ArrayList和在"-client"模式下运行热点时最少.虽然这种性能提升很小并且对于大多数用例来说可以忽略不计,但在某些情况下,这种额外的性能可能会有所不同.例如,如果应该替换一些现有的循环实现,那么库维护者肯定会想要评估Iterable#forEach.
为了用事实支持这个陈述,我已经用Caliper做了一些微基准测试.这是测试代码(需要来自git的最新Caliper):
@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++;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下是结果:
当使用"-client"运行时,Iterable#forEach在ArrayList上优于传统的for循环,但仍然比直接迭代数组慢.当使用"-server"运行时,所有方法的性能大致相同.
为并行执行提供可选支持:这里已经说过,Iterable#forEach使用流并行执行功能接口的可能性当然是一个重要方面.由于Collection#parallelStream()不保证循环实际上是并行执行的,因此必须将此视为可选功能.通过迭代列表list.parallelStream().forEach(...);,你明确地说:这个循环支持并行执行,但它不依赖于它.再次,这是一个功能,而不是赤字!
通过将并行执行的决策从实际的循环实现中移开,您允许对代码进行可选的优化,而不会影响代码本身,这是一件好事.此外,如果默认并行流实现不符合您的需求,则没有人阻止您提供自己的实现.您可以提供优化的集合,具体取决于底层操作系统,集合大小,核心数量以及某些首选项设置:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里的好处是,你的循环实现不需要知道或关心这些细节.
Zho*_*gYu 12
forEach()可以实现比for-each循环更快,因为iterable知道迭代其元素的最佳方式,而不是标准迭代器方式.所以区别在于内部循环或外部循环.
例如,ArrayList.forEach(action)可以简单地实现为
for(int i=0; i<size; i++)
action.accept(elements[i])
Run Code Online (Sandbox Code Playgroud)
而不是需要大量脚手架的for-each循环
Iterator iter = list.iterator();
while(iter.hasNext())
Object next = iter.next();
do something with `next`
Run Code Online (Sandbox Code Playgroud)
但是,我们还需要通过使用来计算两个开销成本forEach(),一个是制作lambda对象,另一个是调用lambda方法.它们可能并不重要.
另请参阅http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/,以比较不同用例的内部/外部迭代.
我觉得我需要补充一点评论...
关于范式\样式
这可能是最值得注意的方面。FP之所以受欢迎,是因为您可以避免副作用。由于这与问题无关,因此我不会深入研究可从中获得哪些利弊。
但是,我会说使用Iterable.forEach进行的迭代是受FP启发的,而是将更多FP引入Java的结果(具有讽刺意味的是,我想说在纯FP中forEach并没有太多用处,因为除了引入外,它没有任何作用副作用)。
最后,我要说的是,这只是您当前正在编写的“品味\样式\范例”问题。
关于并行性。
从性能的角度来看,与foreach(...)相比,使用Iterable.forEach不会带来任何明显的好处。
根据Iterable.forEach的官方文档:
对Iterable的内容执行给定的操作,顺序是元素在迭代时出现,直到所有元素都已处理或该动作引发异常为止。
...即文档非常清楚,不会存在隐式并行性。添加一个将违反LSP。
现在,在Java 8中已承诺有“并行集合”,但是要与我一起使用,您需要更加明确,并格外小心地使用它们(例如,请参阅mschenk74的答案)。
顺便说一句:在这种情况下,将使用Stream.forEach,它不能保证实际工作将并行进行(取决于基础集合)。
更新:可能不那么明显,乍一看有点张扬,但是还有样式和可读性方面的另一个方面。
首先-普通的旧forloop既普通又旧。每个人都已经认识他们。
其次,也是更重要的一点-您可能只想将Iterable.forEach与单层lambda一起使用。如果“身体”变重-他们往往不那么可读。从这里有2个选项-使用内部类(yuck)或使用普通的旧forloop。当人们在相同的代码库中看到相同的事情(集合上的迭代器)时,常常会感到烦恼,这似乎是事实。
同样,这可能是问题,也可能不是问题。取决于从事代码工作的人员。
TL; DR:List.stream().forEach()是最快的.
我觉得我应该从基准测试迭代中添加我的结果.我采用了一种非常简单的方法(没有基准测试框架)并对5种不同的方法进行了基准测试:
forList.forEach()List.stream().forEach()List.parallelStream().forEachprivate 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
}
Run Code Online (Sandbox Code Playgroud)
这个类中的列表应该迭代,并且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);
Run Code Online (Sandbox Code Playgroud)
我在i5 4核CPU上运行了这个版本,java版本为1.8.0_05
forfor(int i = 0, l = list.size(); i < l; ++i) {
doIt(list.get(i));
}
Run Code Online (Sandbox Code Playgroud)
执行时间:4.21毫秒
for(Integer i : list) {
doIt(i);
}
Run Code Online (Sandbox Code Playgroud)
执行时间:5.95毫秒
List.forEach()list.forEach((i) -> doIt(i));
Run Code Online (Sandbox Code Playgroud)
执行时间:3.11毫秒
List.stream().forEach()list.stream().forEach((i) -> doIt(i));
Run Code Online (Sandbox Code Playgroud)
执行时间:2.79毫秒
List.parallelStream().forEachlist.parallelStream().forEach((i) -> doIt(i));
Run Code Online (Sandbox Code Playgroud)
执行时间:3.6毫秒
该功能最大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);
}
Run Code Online (Sandbox Code Playgroud)
以下是有关lambda和流中检查异常处理的其他解决方法的最受欢迎的问题列表:
Java 8:Lambda-Streams,按方法进行过滤(异常)
Java 8:lambda表达式中的强制检查异常处理。为什么是强制性的而不是可选性的?
| 归档时间: |
|
| 查看次数: |
345374 次 |
| 最近记录: |