概述
对于Java语言有点陌生,我试图让自己熟悉所有的方法(或者至少是非病理性的),可以通过列表(或者其他集合)和每个方法的优缺点来迭代。
给定一个Listlist对象,我知道以下遍历所有元素的方法:基本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所指出的,这种形式是一种糟糕的选择。因为实际实现了get方法可能不如使用Iterator时有效。例如,LinkedList实现必须遍历前面的元素得到第i个元素。
在上面的示例中,List的实现无法"保存它的位置"以使未来的迭代更有效。对于一个ArrayList来说,它实际上并不重要,因为get的复杂性/成本是恒定的时间(o(1)),而对于一个LinkedList来说,它与列表的大小(o(n))成比例。
有关内置Collections实现的计算复杂性的更多信息,请查看此问题。增强的for循环(在这个问题中解释得很好)
for (E element : list) {
// 1 - can call methods of element
// ...
}
迭代器
for (Iterator 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
// ...
}
记录器
for (ListIterator 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的流API的映射方法(参见@ IyAM0Z0的答案))
在Java 8中,实现EDCOX1、12、(例如,所有EDCOX1,3,s)的集合类现在有一个EDCOX1×14的方法,它可以用来代替上面演示的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).
如果有的话,还有什么别的办法?
(顺便说一句,我的兴趣根本不是源于对优化性能的渴望;我只是想知道作为开发人员我可以使用哪些表单。)
这些是非病理性的,尽管您也可以使用几个功能风格库中的任何一个来处理集合。
关于List接口的问题是否明确?
@不管出于何种目的,是的,它是特定于List的,但是如果有其他有趣的方法,比如说,与Collection合作,我会有兴趣了解它们。
@戴夫牛顿,谢谢你的想法。我从来没有用过这样的东西。请看一下我编辑过的问题,如果我明白你的意思,请告诉我。
我认为你对增强型for循环的理解是错误的。在循环内部,element是对实际list元素的引用,而不是它的副本。与迭代器类似。实际上,您可以使用这三种样式迭代无法复制的单例(甚至枚举值)列表。
@泰德,在这种情况下,你能解释一下为什么listierationexample.java中的每个部分的迭代器/不会导致numbers发生变化吗?
我认为你应该缩小你的问题范围,并把这些例子作为答案。
我们应该谈论Java 8吗?离现实还有一年!
@sdasdas,完成:stackoverflow.com/questions/18410035/&hellip;
"MarioRossi,是的,请随意列出Java 8中提供的其他技术。也许使用流API?
如果不想将列表映射到新列表中,请不要使用map。
三种循环形式几乎相同。增强型for环路:
for (E element : list) {
. . .
}
根据Java语言规范,它与使用传统EDCOX1×1循环的迭代器的显式使用效果相同。在第三种情况下,只能通过删除当前元素来修改列表内容,然后只有通过迭代器本身的remove方法进行修改。使用基于索引的迭代,您可以自由地以任何方式修改列表。但是,添加或删除当前索引之前的元素可能会导致循环跳过元素或多次处理同一元素;进行此类更改时,需要正确调整循环索引。
在所有情况下,element都是对实际list元素的引用。所有迭代方法都不会复制列表中的任何内容。对element的内部状态的更改将始终显示在列表中相应元素的内部状态中。
本质上,只有两种方法可以迭代列表:使用索引或使用迭代器。增强的for循环只是Java 5中引入的一种语法捷径,以避免显式定义迭代器的单调乏味。对于这两种样式,您可以使用for、while或do while块提出基本上微不足道的变化,但它们都归结为相同的东西(或者更确切地说,是两个东西)。
编辑:正如@ix3在注释中指出的那样,您可以使用ListIterator在迭代时设置列表的当前元素。您需要使用List#listIterator()而不是List#iterator()来初始化循环变量(显然,循环变量必须声明为ListIterator,而不是Iterator)。
好的,谢谢,但是如果迭代器实际上返回了对实数元素的引用(而不是副本),那么如何使用它来更改列表中的值呢?我认为,如果我使用e = iterator.next(),那么执行e = somethingElse只会更改对象e所指的内容,而不是更改iterator.next()从中检索值的实际存储。
@Ix3——在基于索引的迭代中也是如此;将一个新对象分配给e不会改变列表中的内容;您必须调用list.set(index, thing)。您可以更改e的内容(例如,e.setSomething(newValue)的内容),但要在迭代列表时更改存储在列表中的元素,需要坚持基于索引的迭代。
谢谢你的解释;我想我理解你现在所说的,并将相应地更新我的问题/评论。本质上没有"复制",但是由于语言设计的原因,为了更改e的内容,我不得不调用e的方法之一,因为赋值只会更改指针(请原谅我的c)。
因此,对于像Integer和String这样的不可变类型的列表,不可能使用for-each或Iterator方法来更改内容—必须操纵列表对象本身以替换为元素。对吗?
@IX3-正确。可以使用第三种形式(显式迭代器)删除元素,但如果使用基于索引的迭代,则只能向列表中的元素添加元素或替换元素。
再次感谢。顺便说一下,似乎有一个特殊的ListIterator,允许通过set进行更换。我也要把这个添加到列表中。
@Ix3-是的,ListIterator既提供了set又提供了add。我本该想到的。
问题中列出的每种示例:ListIterationExample.java
import java.util.*;
public class ListIterationExample {
public static void main(String []args){
List numbers = new ArrayList();
// 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 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 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 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 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 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());
}
}
不建议使用基本循环,因为您不知道列表的实现。
如果是LinkedList,则每次调用
list.get(i)
将遍历列表,导致n^2时间复杂性。
正确;这是一个很好的观点,我将更新问题中的示例。
根据本文,您的语句是不正确的:哪个更有效,一个for-each循环,还是一个迭代器?
我在那篇文章里读到的正是我所说的…你到底在读什么?
JDK8样式迭代:
public class IterationDemo {
public static void main(String[] args) {
List list = Arrays.asList(1, 2, 3);
list.stream().forEach(elem -> System.out.println("element" + elem));
}
}
谢谢,我打算最终用Java 8的解决方案来更新顶部的列表。
@Eugene82这种方法更有效吗?
@纳扎尔艺术,除非你在一个非常大的收藏中使用平行流,否则你不会看到比其他任何东西有很大的改进。它只在治疗冗长的感觉上更有效,真的。
在Java 8中,我们有多种方式对集合类进行迭代。使用Iterable ForEach
实现Iterable的集合(例如所有列表)现在有forEach方法。我们可以使用Java 8中介绍的方法引用。
Arrays.asList(1,2,3,4).forEach(System.out::println);
使用流foreach和foreachored
我们还可以使用流迭代列表,如下所示:
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
Arrays.asList(1,2,3,4).stream().forEachOrdered(System.out::println);
我们应该更喜欢forEachOrdered而不是forEach,因为forEach的行为是明确的不确定性的,当forEachOrdered为该流的每个元素执行一个动作时,如果该流有一个定义的遭遇顺序,则按照该流的遭遇顺序执行。所以foreach不能保证订单能被保存。
流的优势在于,我们还可以在适当的情况下使用并行流。如果目标只是打印项目,不管顺序如何,那么我们可以使用并行流作为:
Arrays.asList(1,2,3,4).parallelStream().forEach(System.out::println);
我不知道你认为什么是病理性的,但让我提供一些你以前没见过的替代品:
List sl= list ;
while( ! sl.empty() ) {
E element= sl.get(0) ;
.....
sl= sl.subList(1,sl.size());
}
或其递归版本:
void visit(List list) {
if( list.isEmpty() ) return;
E element= list.get(0) ;
....
visit(list.subList(1,list.size()));
}
另外,经典for(int i=0...的递归版本:
void visit(List list,int pos) {
if( pos >= list.size() ) return;
E element= list.get(pos) ;
....
visit(list,pos+1);
}
我提到它们是因为你对Java有点"新",这可能很有趣。
谢谢你的提醒。我从来没有看过子列表的方法。是的,我认为这是病态的,因为我不知道在任何情况下,除了可能的模糊竞争之外,使用它将是有好处的。
好啊。我不喜欢被称为"病态的",所以这里有一个解释:我在跟踪或跟踪树上的路径时使用了它们。再一个?在把一些LISP程序翻译成Java时,我不想让他们失去LISP精神,也做同样的事情。如果你认为这些都是有效的,非病理性的用法,那就给评论投赞成票。我需要一个集体拥抱!!!!-)
树上的路径和列表不同吗?顺便说一句,我不是要叫你或任何人"病态的",我只是说,我的问题的一些答案将是可以理解的不切实际或非常不可能在良好的软件工程实践中有价值。
@别担心,我只是开玩笑。路径是列表:"从根开始"(明显),"移动到第二个孩子","移动到第一个孩子","移动到第四个孩子"。停下来。或者简称为[2,1,4]。这是一张单子。
我希望创建一个列表的副本并使用while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }。这比第一个版本更有效;)。
可以从Java 8开始使用Frace:
List nameList = new ArrayList<>(
Arrays.asList("USA","USSR","UK"));
nameList.forEach((v) -> System.out.println(v));
在java 8中,可以使用List.forEach()方法和lambda expression迭代列表。< BR>
import java.util.ArrayList;
import java.util.List;
public class TestA {
public static void main(String[] args) {
List list = new ArrayList();
list.add("Apple");
list.add("Orange");
list.add("Banana");
list.forEach(
(name) -> {
System.out.println(name);
}
);
}
}
酷。你能解释一下这和eugene82和i_am_zero的答案有什么不同吗?
@ IX3 AHH。没有阅读完整的答案列表。想帮忙……你要我删除答案吗?
对我来说并不重要,不过也许你可以通过将它与其他技术进行比较和对比来改进它。或者,如果您认为它没有演示任何新技术,但可能仍然对其他人有用,那么您可以添加一个注释来解释这一点。
对于向后搜索,应使用以下内容:
for (ListIterator iterator = list.listIterator(list.size()); iterator.hasPrevious();) {
SomeClass item = iterator.previous();
...
item.remove(); // For instance.
}
如果要知道位置,请使用Iterator.PreviousIndex()。它还有助于编写一个内部循环来比较列表中的两个位置(迭代器不相等)。
对,列出了许多备选方案。最简单、最干净的方法就是使用下面的增强型for语句。Expression是某种不可测的类型。
for ( FormalParameter : Expression ) Statement
例如,要遍历listids,我们可以简单地这样做,
for (String str : ids) {
// Do something
}
他在问除了问题中描述的方法(包括你提到的这个)之外,还有没有其他方法!
您可以使用while循环和更多的代码来切换第一个和第三个示例。这使您能够在以下情况下使用Do:
int i = 0;
do{
E element = list.get(i);
i++;
}
while (i < list.size());
当然,如果list.size()返回0,这种情况可能会导致nullPointerException,因为它总是至少执行一次。这可以通过在使用元素的属性/方法tho之前测试元素是否为空来解决。不过,使用for循环要简单得多。
是的……我想这在技术上是不同的,但是只有当列表是空的时候,行为才是不同的,对吧?
@Ix3是的,在这种情况下,行为更糟。我不认为这是一个好的选择。
当然,但公平地说,这个问题不是关于什么是"好的",而是关于什么是"可能的",所以我仍然很感激这个答案。
最后
以上就是纯情音响为你收集整理的在java中迭代,关于循环:在Java中迭代列表的方法的全部内容,希望文章能够帮你解决在java中迭代,关于循环:在Java中迭代列表的方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复