对于Java语言有些新意,我试图让自己熟悉一个可能遍历列表(或者可能是其他集合)以及每个集合的优点或缺点的所有方法(或者至少是非病态方法) .
给定一个 List<E> list
对象,我知道以下循环遍历所有元素的方法:
Basic for循环(当然,还有等同的while / do while循环)
// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
注意:正如@amarseillan所指出的,这种形式对于迭代 List
是一个不好的选择,因为 get
方法的实际实现可能不如使用 Iterator
时那样有效 . 例如, LinkedList
实现必须遍历i之前的所有元素以获取第i个元素 .
在上面的示例中, List
实现"save its place"无法使未来的迭代更有效 . 对于 ArrayList
并不重要,因为 get
的复杂性/成本是恒定时间(O(1))而对于 LinkedList
它是否与列表的大小成比例(O(n)) .
有关内置 Collections
实现的计算复杂性的更多信息,请查看this question .
增强for循环(在这个问题中很好地解释)
for (E element : list) {
// 1 - can call methods of element
// ...
}
迭代器
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
ListIterator
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
功能Java
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach,Stream.forEach,...
(来自Java 8的Stream API的map方法(参见@ i_am_zero的回答) . )
在Java 8中,实现 Iterable
的集合类(例如,所有 List
s)现在都有 forEach
方法,可以使用它来代替上面演示的for loop statement . (这是another question提供了一个很好的比较 . )
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
还有什么其他方式,如果有的话?
(顺便说一句,我的兴趣并不是出于对optimize performance的渴望;我只是想知道作为开发人员我可以使用哪些形式 . )
11 回答
是的,列出了许多替代品 . 最简单,最干净的只是使用增强的
for
语句,如下所示 .Expression
属于可迭代的某种类型 .例如,要遍历List <String> id,我们可以这样做,
在
java 8
中,您可以使用List.forEach()
方法和lambda expression
迭代列表 .对于向后搜索,您应该使用以下内容:
如果您想知道某个职位,请使用iterator.previousIndex() . 它还有助于编写一个比较列表中两个位置的内部循环(迭代器不相等) .
我不知道你认为什么病态,但让我提供一些你以前没见过的替代方案:
或者它的递归版本:
另外,经典
for(int i=0...
的递归版本:我提到它们是因为你“对Java有点新”,这可能很有趣 .
在 Java 8 中,我们有多种方法来迭代集合类 .
使用Iterable forEach
实现
Iterable
的集合(例如所有列表)现在具有forEach
方法 . 我们可以使用Java 8中引入的method-reference .使用Streams forEach和forEachOrdered
我们还可以使用Stream迭代列表:
我们应该更喜欢
forEachOrdered
而不是forEach
因为forEach
的行为明确是非确定性的,因为forEachOrdered
对流的每个元素执行一个动作,如果流具有已定义的遭遇顺序,则按流的遭遇顺序 . 所以forEach不保证订单会被保留 .流的优点是我们也可以在适当的地方使用并行流 . 如果目标只是打印项目而不管订单如何,那么我们可以使用并行流作为:
您总是可以使用while循环和更多代码来切换第一个和第三个示例 . 这为您提供了使用do-while的优势:
当然,如果list.size()返回0,这种事情可能会导致NullPointerException,因为它总是至少执行一次 . 这可以通过在使用其属性/方法之前测试if是否为null来修复 . 尽管如此,使用for循环还是更加简单和容易
问题中列出的每种示例:
ListIterationExample.java
建议不要使用基本循环,因为您不知道列表的实现 .
如果那是一个LinkedList,则每次调用
将迭代列表,导致N ^ 2时间复杂度 .
三种形式的循环几乎相同 . 增强的
for
循环:根据Java Language Specification,与使用传统
for
循环的迭代器的显式使用相同 . 在第三种情况下,您只能通过删除当前元素来修改列表内容,然后才能通过迭代器本身的remove
方法来执行此操作 . 同基于索引的迭代,您可以以任何方式自由修改列表 . 但是,添加或删除当前索引之前的元素可能会导致循环跳过元素或多次处理同一元素;进行此类更改时,需要正确调整循环索引 .在所有情况下,
element
是对实际列表元素的引用 . 没有迭代方法可以复制列表中的任何内容 . 始终会在列表中相应元素的内部状态中看到对element
内部状态的更改 .实质上,只有两种方法可以迭代列表:使用索引或使用迭代器 . 增强的for循环只是Java 5中引入的一个语法快捷方式,以避免显式定义迭代器的繁琐 . 对于这两种样式,你可以使用
for
,while
或do while
块来提出基本上微不足道的变体,但它们都归结为相同的东西(或者更确切地说,两件事) .编辑:正如@ iX3在评论中指出的那样,您可以使用
ListIterator
在迭代时设置列表的当前元素 . 您需要使用List#listIterator()而不是List#iterator()来初始化循环变量(显然,它必须声明为ListIterator
而不是Iterator
) .JDK8风格的迭代:
您可以从Java 8开始使用forEach: