【java细节】for循环,for-each(增强for循环),forEach以及Iterator迭代器对比
一、背景和结论
背景是这样的,产品开发过程中,有个字体json文件需要截取的接口开发,因为涉及到多层的json内容遍历,于是就和循环过不去了。这篇文章主要讨论java中for循环,for-each(增强for循环),forEach以及Iterator迭代器的区别和联系。结论先行:
联系:
- 1.作用相同.都是用于队数组或者集合进行遍历
区别:
- 效率差别: for循环,for-each循环和Iterator效率差不多,forEach要慢些。
- 条件区别: for循环需要知道数组或者集合的大小才能进行遍历,而for-each,forEach和Iterator不需要
-
多态区别: for循环,for-each和forEach都需要先知道集合的类型,甚至是集合内元素的类型,即需要访问内部的成员,不能实现多态;
Iterator是一个接口类型,他不关心集合或者数组的类型,而且他还能随时修改和删除集合的元素,举个例子:
Iterator iterator = integerList.iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
}
当我们需要遍历不同的集合时,我们只需要传递集合的iterator,如integerList.iterator(),这就 是Iterator 的好处,他不包含任何有关他所遍历的集合的类型信息,能够将遍历集合的操作与集合底层的数据结构分离。迭代器统一了对集合的访问方式。这也是接口解耦的最佳实践之一。
-
适用场景区别:
for循环一般用来处理比较复杂的循环,可预知大小的集合或数组。因为涉及索引的操作,**可能会由于开发者的问题导致索引混乱,因此不太安全。**涉及集合元素更新操作的建议用for循环。
for-each可用于遍历任何集合或数组,而且操作简单易懂,但是需要了解集合内部数据类型,并且不适合有数组元素更新操作的循环。
forEach 基本和Iterator类似。
Iterator是最强大的,他可以在不需要知道元素和集合的类型的情况下随时修改或者删除集合内部的元素,当你需要对不同的容器实现同样的遍历方式时,迭代器是最好的选择。
二、内容
下面是我所用的for循环, for-each,forEach以及Iterator迭代器效率测试程序和效果截图:
public class ForIteratorForEachTest {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
Integer intValue = 0;
AtomicReference<Integer> value = new AtomicReference<>(0);
long size = 100000;
// 初始化待测试集合
for (int i = 0; i < size; i++) {
integerList.add(i);
}
// 开始时间
long startTime = System.nanoTime();
// for循环
/*for (int i = 0; i < size; i++) {
intValue = integerList.get(i);
}*/
// for-each 循环
/* for (Integer integer : integerList) {
intValue = integer;
}*/
// forEach 循环
/*integerList.forEach((integer) -> {
value.set(integer);
});*/
// iterator
Iterator iterator = integerList.iterator();
while (iterator.hasNext()) {
Integer integer = (Integer) iterator.next();
}
// 结束时间
long endTime = System.nanoTime();
System.out.println("#========= 方法耗时: " + (endTime - startTime) + " 纳秒 =========#");
}
}
for循环,for-each循环(增强for循环)和Iterator效率差不多,甚至在数据量小的情况下,for循环效率更高。在10w,100w,1000w三种级别forEach都是最慢的。通过翻JDK1.8的源码源码我发现了原因:
1.每个元素遍历前都进行了判空操作。
2.因为是流管道操作,所以他的元素数据结构需要保证原子性,这里又是一层性能消耗。
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
// 先判空
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
三、延伸
Java神书之一 《Effective Java》 中第九章,58节提到,Prefer for-each loops to traditional for loops(for-each 循环优于传统的 for 循环),他给出的原因是:
”迭代器和索引变量都很混乱(你只需要元素)。此外,它们有出错的可能。迭代器在每个循环中出现三次,索引变量出现四次,这使得有很多机会使用到错误的变量。如果这样做,就不能保证编译器会捕捉到问题。最后,这两个循环区别很大,for循环还需要额外注意容器类型,并给类型转换增加小麻烦。“for-each 循环(官方称为「enhanced for 语句」)解决了所有这些问题。它通过隐藏迭代器或索引变量来消除混乱和出错的机会。文中也指出了三个for-each循环不适应的场景:
- 破坏性过滤
如果需要遍历一个集合并删除选定元素,则需要使用显式的迭代器,以便调用其 remove 方法。通过使用 Collection 在 Java 8 中添加的 removeIf 方法,通常可以避免显式遍历。
- 转换
如果需要遍历一个 List 或数组并替换其中部分或全部元素的值,那么需要 List 迭代器或数组索引来替换元素的值。
- 并行迭代
如果需要并行遍历多个集合,那么需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步执行(如上述牌和骰子示例中无意中演示的错误那样)。如果发现自己处于这些情况中的任何一种,请使用普通的 for 循环,并警惕本条目中提到的陷阱。
参考资料:
https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual