Java:在迭代过程中将元素添加到集合中

迭代时可以向集合中添加元素吗?

更具体地说,我想迭代一个集合,如果一个元素满足一定的条件,我想添加一些其他元素的集合,并确保这些添加的元素也迭代。 (我意识到这可能会导致一个无限循环,但我很确定这不会在我的情况。)

Sun的Java教程build议这样做是不可能的:“请注意, Iterator.remove是在迭代期间修改集合的唯一安全方法;如果在迭代过程中以其他任何方式修改了底层集合,则行为是未指定的。 “

所以如果我不能用迭代器做我想做的事情,你build议我做什么?

如何用你想迭代的元素构build一个Queue? 当您想要添加元素时,将它们排入队列末尾,并保持删除元素,直到队列为空。 这是一个广度优先search通常如何工作。

这里有两个问题:

第一个问题是,在Iterator返回之后添加到Collection 。 如前所述,在修改底层Collection时没有定义的行为,如Iterator.remove的文档中所述:

…迭代器的行为是未指定的,如果在迭代过程中除了通过调用这个方法以外的任何其他方式来修改底层集合。

第二个问题是,即使可以获得Iterator ,然后返回到Iterator所在的同一个元素,也不能保证迭代的顺序,如Collection.iterator方法文档中所述:

…没有关于元素返回顺序的保证(除非这个集合是某个提供保证的类的实例)。

例如,假设我们有这个列表[1, 2, 3, 4]

假设当Iterator3时添加了5 ,并且我们得到了一个Iterator ,它可以从4迭代。 但是,没有保证5会在4之后。 迭代次序可以是[5, 1, 2, 3, 4] 5,1,2,3,4 [5, 1, 2, 3, 4] – 那么迭代器仍然会丢失元素5

由于行为不能保证,人们不能认为事情会以某种方式发生。

一个替代scheme可以是将新创build的元素添加到单独的Collection ,然后遍历这些元素:

 Collection<String> list = Arrays.asList(new String[]{"Hello", "World!"}); Collection<String> additionalList = new ArrayList<String>(); for (String s : list) { // Found a need to add a new element to iterate over, // so add it to another list that will be iterated later: additionalList.add(s); } for (String s : additionalList) { // Iterate over the elements that needs to be iterated over: System.out.println(s); } 

编辑

对Avi的答案进行详细阐述,可以将我们想要迭代的元素排队到队列中,并在队列中包含元素的时候删除元素。 这将允许除了原始元素之外的新元素的“迭代”。

让我们看看它是如何工作的。

从概念上讲,如果我们在队列中有以下元素:

[1, 2, 3, 4]

而且,当我们删除1 ,我们决定添加42 ,队列将如下所示:

[2, 3, 4, 42]

由于队列是FIFO (先入先出)数据结构,因此这种sorting是典型的。 (正如Queue接口的文档中所指出的那样,这不是Queue的必要条件,而是以PriorityQueue顺序来sorting元素,所以这不是FIFO。)

以下是使用LinkedList (这是一个Queue )的例子,以便通过所有元素以及在出列期间添加的其他元素。 类似于上面的例子,当元素2被移除时,元素42被添加:

 Queue<Integer> queue = new LinkedList<Integer>(); queue.add(1); queue.add(2); queue.add(3); queue.add(4); while (!queue.isEmpty()) { Integer i = queue.remove(); if (i == 2) queue.add(42); System.out.println(i); } 

结果如下:

 1 2 3 4 42 

正如所希望的,当我们击中2时join的元素42出现了。

你也可能想看看一些更专业的types,比如ListIterator , NavigableSet和(如果你对地图感兴趣的话) NavigableMap 。

其实这相当简单。 只要想想最佳的方式。 我相信最好的办法是:

 for (int i=0; i<list.size(); i++) { Level obj = list.get(i); //Here execute yr code that may add / or may not add new element(s) //... i=list.indexOf(obj); } 

下面的例子在最合乎逻辑的情况下完美地工作 – 当你不需要在迭代元素之前迭代添加的新元素时。 关于迭代元素之后添加的元素 – 你可能不想迭代它们。 在这种情况下,您应该简单地添加/或扩展yr对象的标志,标志他们不要迭代它们。

使用迭代器…不,我不这么认为。 你将不得不一起破解这样的事情:

  Collection< String > collection = new ArrayList< String >( Arrays.asList( "foo", "bar", "baz" ) ); int i = 0; while ( i < collection.size() ) { String curItem = collection.toArray( new String[ collection.size() ] )[ i ]; if ( curItem.equals( "foo" ) ) { collection.add( "added-item-1" ); } if ( curItem.equals( "added-item-1" ) ) { collection.add( "added-item-2" ); } i++; } System.out.println( collection ); 

哪个yeilds:
[foo,bar,baz,added-item-1,added-item-2]

 public static void main(String[] args) { // This array list simulates source of your candidates for processing ArrayList<String> source = new ArrayList<String>(); // This is the list where you actually keep all unprocessed candidates LinkedList<String> list = new LinkedList<String>(); // Here we add few elements into our simulated source of candidates // just to have something to work with source.add("first element"); source.add("second element"); source.add("third element"); source.add("fourth element"); source.add("The Fifth Element"); // aka Milla Jovovich // Add first candidate for processing into our main list list.addLast(source.get(0)); // This is just here so we don't have to have helper index variable // to go through source elements source.remove(0); // We will do this until there are no more candidates for processing while(!list.isEmpty()) { // This is how we get next element for processing from our list // of candidates. Here our candidate is String, in your case it // will be whatever you work with. String element = list.pollFirst(); // This is where we process the element, just print it out in this case System.out.println(element); // This is simulation of process of adding new candidates for processing // into our list during this iteration. if(source.size() > 0) // When simulated source of candidates dries out, we stop { // Here you will somehow get your new candidate for processing // In this case we just get it from our simulation source of candidates. String newCandidate = source.get(0); // This is the way to add new elements to your list of candidates for processing list.addLast(newCandidate); // In this example we add one candidate per while loop iteration and // zero candidates when source list dries out. In real life you may happen // to add more than one candidate here: // list.addLast(newCandidate2); // list.addLast(newCandidate3); // etc. // This is here so we don't have to use helper index variable for iteration // through source. source.remove(0); } } } 

例如,我们有两个列表:

  public static void main(String[] args) { ArrayList a = new ArrayList(Arrays.asList(new String[]{"a1", "a2", "a3","a4", "a5"})); ArrayList b = new ArrayList(Arrays.asList(new String[]{"b1", "b2", "b3","b4", "b5"})); merge(a, b); a.stream().map( x -> x + " ").forEach(System.out::print); } public static void merge(List a, List b){ for (Iterator itb = b.iterator(); itb.hasNext(); ){ for (ListIterator it = a.listIterator() ; it.hasNext() ; ){ it.next(); it.add(itb.next()); } } } 

a1 b1 a2 b2 a3 b3 a4 b4 a5 b5

我更喜欢在function上处理集合,而不是将它们改变。 这完全避免了这种问题,以及别名问题和其他棘手的错误来源。

所以,我会执行它像:

 List<Thing> expand(List<Thing> inputs) { List<Thing> expanded = new ArrayList<Thing>(); for (Thing thing : inputs) { expanded.add(thing); if (needsSomeMoreThings(thing)) { addMoreThingsTo(expanded); } } return expanded; } 

恕我直言,更安全的方法是创build一个新的集合,遍历给定的集合,添加新集合中的每个元素,并在新集合中添加所需的额外元素,最后返回新的集合。

除了使用附加列表和调用addAll以在迭代之后插入新项目(例如用户Nat的解决scheme)的解决scheme之外,还可以使用像CopyOnWriteArrayList这样的并发集合。

“快照”样式迭代器方法在创build迭代器时使用对数组状态的引用。 这个数组在迭代器的生命周期中永远不会改变,所以干扰是不可能的,迭代器保证不抛出ConcurrentModificationException。

有了这个特殊的集合(通常用于并发访问),可以在迭代时操纵底层列表。 但是,迭代器不会反映这些变化。

这比其他解决scheme好吗? 可能不是,我不知道由写入时复制方法引入的开销。

给定一个列表List<Object>你想要迭代,简单的方法是:

 while (!list.isEmpty()){ Object obj = list.get(0); // do whatever you need to // possibly list.add(new Object obj1); list.remove(0); } 

所以,你遍历一个列表,总是把第一个元素,然后删除它。 这样,您可以在迭代时将新元素附加到列表中。

忘记迭代器,它们不适用于添加,仅用于删除。 我的答案只适用于名单,所以不要惩罚我不解决收集问题。 坚持基础:

  List<ZeObj> myList = new ArrayList<ZeObj>(); // populate the list with whatever ........ int noItems = myList.size(); for (int i = 0; i < noItems; i++) { ZeObj currItem = myList.get(i); // when you want to add, simply add the new item at last and // increment the stop condition if (currItem.asksForMore()) { myList.add(new ZeObj()); noItems++; } } 

我累了ListIterator,但它并没有帮助我的情况下,你必须使用列表,而添加到它。 以下是对我有用的东西:

使用LinkedList

 LinkedList<String> l = new LinkedList<String>(); l.addLast("A"); while(!l.isEmpty()){ String str = l.removeFirst(); if(/* Condition for adding new element*/) l.addLast("<New Element>"); else System.out.println(str); } 

这可能会导致exception或运行到无限循环。 但是,正如你所提到的那样

我很确定这不会在我的情况下

在这样的代码检查angular落案件是你的责任。

这是我通常做的,像集合的集合:

 Set<T> adds = new HashSet<T>, dels = new HashSet<T>; for ( T e: target ) if ( <has to be removed> ) dels.add ( e ); else if ( <has to be added> ) adds.add ( <new element> ) target.removeAll ( dels ); target.addAll ( adds ); 

这会创build一些额外的内存(中间集合的指针,但不会有重复的元素发生)和额外的步骤(通过更改迭代),但通常这不是什么大事,它可能比使用初始集合副本更好。

即使我们不能在迭代过程中将项目添加到同一个列表中,我们也可以使用Java 8的flatMap来为stream添加新的元素。 这可以在一个条件下完成。 在此之后,可以处理添加的项目。

以下是一个Java示例,其中显示了如何根据条件将正在处理的对象添加到正在处理的stream中,然后使用条件进行处理:

 List<Integer> intList = new ArrayList<>(); intList.add(1); intList.add(2); intList.add(3); intList = intList.stream().flatMap(i -> { if (i == 2) return Stream.of(i, i * 10); // condition for adding the extra items return Stream.of(i); }).map(i -> i + 1) .collect(Collectors.toList()); System.out.println(intList); 

玩具例子的输出是:

[2,3,21,4]

一般来说 ,这是不安全的,虽然对于一些collections它可能是。 明显的select是使用某种for循环。 但是你没有说你正在使用什么样的集合,所以可能或不可能。