我和我的朋友就此发生了争执。 请考虑以下代码段
for(i=0; i<someList.size(); i++) {
//some logic
}
此处someList.size()
将在每次迭代时执行,因此建议将此大小计算迁移到循环之外(之前)。
现在当我使用像这样的扩展for循环时会发生什么,
for(SpecialBean bean: someBean.getSpecialList()) {
//some logic
}
是否有必要将someBean.getSpecialList()
移到循环之外?
如果我要保留第二个片段,someBean.getSpecialList()
会执行多少次?
答案 0 :(得分:8)
重复调用list.size()
不会导致任何性能损失。 JIT编译器最有可能内联它,即使它没有,它仍然会相当便宜,因为它只涉及读取字段的值。
第一个例子的一个更严重的问题是循环体必须涉及list.get(i)
而对于LinkedList
,进行 i 元素有O( i )由于指针追踪而成本具有非常显着的常数因子,这转化为CPU级别上与数据相关的负载。 CPU的预取器无法优化此访问模式。
这意味着当应用于LinkedList
时,整体计算复杂度将为O(n 2 )。
您的第二个示例通过Iterator
编译为迭代,并且仅评估someBean.getSpecialList().iterator()
一次。 iterator.next()
的费用在所有情况下都是不变的。
答案 1 :(得分:4)
来自Joshua Bloch的Effective Java中的第46项:
在1.5版中引入的for-each循环消除了混乱 以及隐藏迭代器或索引的错误机会 完全变量。由此产生的习语同样适用于 集合和数组:
//迭代集合和数组的首选习惯用法 (元素e:元素){ doSomething的(E);当你看到冒号(:)时,将其读作“in”。因此,上面的循环读作“为元素中的每个元素e”。 即使使用for-each循环也没有性能损失 对于数组。事实上,它可能会提供轻微的性能优势 在某些情况下,一个普通的for循环,因为它计算限制 数组索引只有一次。虽然你可以手工完成(第45项), 程序员并不总是这样做。
另见is-there-a-performance-difference-between-a-for-loop-and-a-for-each-loop
答案 2 :(得分:0)
for each
变体将与以下相同
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomething((Element) i.next());
}
从项目46:首选 - 每个循环到传统for循环的有效java
for-each循环提供了超越传统的强大优势 清晰度和防止错误的循环,没有性能损失。您 应该尽可能地使用它。
所以我的第一个猜测是错误的,使用for each
循环内的函数没有惩罚。
答案 3 :(得分:0)
第一个代码段的替代方法是:
for(i=0, l=someList.size(); i<l; i++) {
//some logic
}
关于for..each循环,对getSpecialList()
的调用只会进行一次(您可以通过在方法中添加一些调试/日志来验证这一点。)
答案 4 :(得分:0)
由于扩展循环使用从Iterable中取出的迭代器,因此多次执行someBean.getSpecialList()
是不可能或不明智的。将它移出循环不会改变循环的性能,但如果它提高了可读性,你就可以做到。
注意:如果您按索引进行迭代,则随机访问集合可能会更快,例如ArrayList,因为它不会创建迭代器,但对于不支持随机访问的索引集合来说速度较慢。