在Java中迭代列表的方法?
对于Java语言来说,我尝试熟悉所有的方法(或者至less是非病态方法),以便可以遍历列表(或者其他集合)以及每个方法的优缺点。
给定一个List<E> list
对象,我知道以下方法遍历所有元素:
基本的 循环 (当然,在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
s来说是一个糟糕的select,因为get
方法的实际实现可能不如使用Iterator
时那样高效。 例如, LinkedList
实现必须遍历i之前的所有元素才能获得第i个元素。 在上面的例子中, List
实现没有办法“保存它的位置”,以便将来的迭代更有效率。 对于一个ArrayList
它并不重要,因为get
的复杂性/代价是恒定的时间(O(1)),而对于一个LinkedList
,它与列表的大小成正比(O(n))。 有关内置Collections
实现的计算复杂性的更多信息,请查看此问题 。
增强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
的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 // ... }
编辑:添加“function风格”的解决scheme(感谢戴夫牛顿)
function性的Java
list.stream().map(e -> e + 1); // can apply a transformation function for e
编辑:添加从Java 8的Stream API的地图方法(请参阅@ i_am_zero的答案)
Iterable.forEach , Stream.forEach ,…
在实现Iterable
(例如所有List
)的Java 8集合类中,现在有一个forEach
方法,可以使用它来代替上面演示的for循环语句 。 (这是另一个问题 ,提供了一个很好的比较。)
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.)
还有什么其他的方式,如果有的话?
我觉得这是重复的,但是我一直没有find我要找的东西,所以我对这个问题可能是多余的道歉。 (顺便说一下,我的兴趣根本不在于优化性能的愿望;我只想知道作为开发人员可以使用哪些forms)。
编辑:将ListIterationExample.java移动到build议的答案
循环的三种forms几乎相同。 增强的for
循环:
for (E element : list) { . . . }
根据Java语言规范 , 与传统的for
循环显式使用迭代器相同 。 在第三种情况下,只能通过删除当前元素来修改列表内容,然后只能通过迭代器本身的remove
方法来完成。 使用基于索引的迭代,您可以自由地以任何方式修改列表。 但是,添加或删除当前索引之前的元素可能会导致循环跳过元素或处理相同的元素多次; 您需要在进行此类更改时正确调整循环索引。
在所有情况下, element
是对实际列表元素的引用。 迭代方法都不会在列表中生成任何副本。 element
内部状态的改变将总是在列表中相应元素的内部状态中看到。
从本质上讲,只有两种方法可以遍历列表:使用索引或使用迭代器。 增强的for循环只是Java 5中引入的一种语法快捷方式,可以避免显式定义迭代器的麻烦。 对于这两种风格,你可以想出使用for
, while
或者block的基本微不足道的变体,但是它们都归结为相同的东西(或者说,两件事情)。
编辑:由于@ iX3在注释中指出,您可以使用ListIterator来设置列表的当前元素,因为您正在迭代。 您将需要使用List#listIterator()
而不是List#iterator()
来初始化循环variables(显然,必须将其声明为ListIterator
而不是Iterator
)。
问题中列出的每种types的示例:
ListIterationExample.java
import java.util.*; public class ListIterationExample { public static void main(String []args){ List<Integer> numbers = new ArrayList<Integer>(); // populates list with initial values for (Integer i : Arrays.asList(0,1,2,3,4,5,6,7)) numbers.add(i); printList(numbers); // 0,1,2,3,4,5,6,7 // replaces each element with twice its value for (int index=0; index < numbers.size(); index++) { numbers.set(index, numbers.get(index)*2); } printList(numbers); // 0,2,4,6,8,10,12,14 // does nothing because list is not being changed for (Integer number : numbers) { number++; // number = new Integer(number+1); } printList(numbers); // 0,2,4,6,8,10,12,14 // same as above -- just different syntax for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) { Integer number = iter.next(); number++; } printList(numbers); // 0,2,4,6,8,10,12,14 // ListIterator<?> provides an "add" method to insert elements // between the current element and the cursor for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) { Integer number = iter.next(); iter.add(number+1); // insert a number right before this } printList(numbers); // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 // Iterator<?> provides a "remove" method to delete elements // between the current element and the cursor for (Iterator<Integer> iter = numbers.iterator(); iter.hasNext(); ) { Integer number = iter.next(); if (number % 2 == 0) // if number is even iter.remove(); // remove it from the collection } printList(numbers); // 1,3,5,7,9,11,13,15 // ListIterator<?> provides a "set" method to replace elements for (ListIterator<Integer> iter = numbers.listIterator(); iter.hasNext(); ) { Integer number = iter.next(); iter.set(number/2); // divide each element by 2 } printList(numbers); // 0,1,2,3,4,5,6,7 } public static void printList(List<Integer> numbers) { StringBuilder sb = new StringBuilder(); for (Integer number : numbers) { sb.append(number); sb.append(","); } sb.deleteCharAt(sb.length()-1); // remove trailing comma System.out.println(sb.toString()); } }
不build议使用基本循环,因为您不知道列表的实现。
如果这是一个LinkedList,每个呼叫
list.get(i)
将在列表上迭代,导致N ^ 2时间复杂度。
JDK8风格的迭代:
public class IterationDemo { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3); list.stream().forEach(elem -> System.out.println("element " + elem)); } }
我不知道你认为是病态的,但是让我提供一些你以前从未见过的select:
List<E> sl= list ; while( ! sl.empty() ) { E element= sl.get(0) ; ..... sl= sl.subList(1,sl.size()); }
或者它的recursion版本:
void visit(List<E> list) { if( list.isEmpty() ) return; E element= list.get(0) ; .... visit(list.subList(1,list.size())); }
另外,经典的recursion版本for(int i=0...
:
void visit(List<E> list,int pos) { if( pos >= list.size() ) return; E element= list.get(pos) ; .... visit(list,pos+1); }
我提到他们是因为你“对Java有点新鲜”,这可能是有趣的。
在实现Iterable
(例如所有列表)的Java 8集合类中,现在有forEach
方法:
Arrays.asList(1,2,3,4).forEach(System.out::println);
上面的例子使用了Java 8中引入的方法引用 。我们也可以使用Stream迭代一个列表,如下所示:
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
后面的选项的好处是,我们也可以在适当的地方使用并行stream。 如果目标只是打印项目而不考虑顺序,那么我们可以使用并行stream:
Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
对于向后search,您应该使用以下内容:
for (ListIterator<SomeClass> iterator = list.listIterator(list.size()); iterator.hasPrevious();) { SomeClass item = iterator.previous(); ... item.remove(); // For instance. }
如果你想知道一个位置,使用iterator.previousIndex()。 它也有助于编写一个比较列表中两个位置的内部循环(迭代器不相等)。
对,列出的许多替代方法,最简单和最干净的就是使用下面的增强型语句,expression式是可迭代的某种types。
for ( FormalParameter : Expression ) Statement
例如,要遍历List id,我们可以简单地这样做,
for (String str : ids) { //do something }
您可以随时使用while循环和更多的代码来转换第一个和第三个示例。 这给了你能够使用do-while的优点:
int i = 0; do{ E element = list.get(i); i++; } while (i < list.size());
当然,如果list.size()返回0,这种事情可能会导致NullPointerException,因为它总是被执行至less一次。 这可以通过在使用其属性/方法之前testing元素是否为空来解决。 不过,使用for循环要简单得多