是否可以在迭代时向元素添加元素?
更具体地说,我想迭代一个集合,如果一个元素满足某个条件,我想在集合中添加一些其他元素,并确保迭代这些添加的元素。 (我意识到这个可以导致一个无终止的循环,但我很确定它不会出现在我的情况下。)
来自Sun的Java Tutorial表明这是不可能的:“请注意Iterator.remove
是在迭代期间修改集合的唯一安全方式;如果是,则行为未指定在迭代进行过程中,底层集合会以任何其他方式进行修改。“
因此,如果我不能使用迭代器做我想做的事情,你建议我做什么?
答案 0 :(得分:59)
如何使用要迭代的元素构建Queue;当您想要添加元素时,将它们排入队列的末尾,并继续删除元素,直到队列为空。这就是广度优先搜索通常的工作方式。
答案 1 :(得分:46)
这里有两个问题:
第一个问题是,在Collection
返回后添加到Iterator
。如上所述,在修改基础Collection
时没有定义的行为,如Iterator.remove
的文档中所述:
...迭代器的行为是 未指定,如果底层 集合被修改而 迭代正在以任何方式进行 除了通过调用此方法。
第二个问题是,即使可以获得Iterator
,然后返回到Iterator
所在的同一元素,也无法保证迭代的顺序,如Collection.iterator
方法文档:
......对此没有任何保证 元素的顺序 返回(除非此集合是 某个类的实例提供了一个 保证)。
例如,假设我们有列表[1, 2, 3, 4]
。
假设5
位于Iterator
时添加3
,不知何故,我们得到Iterator
可以从4
恢复迭代。但是,5
之后4
没有任何保证。迭代顺序可能是[5, 1, 2, 3, 4]
- 然后迭代器仍然会遗漏元素5
。
由于无法保证这种行为,人们不能认为事情会以某种方式发生。
一种替代方法可能是将新创建的元素添加到其中的单独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's answer上进行阐述,可以将我们想要迭代的元素排队到队列中,并在队列中包含元素时删除元素。除了原始元素之外,这将允许对新元素进行“迭代”。
让我们来看看它是如何运作的。
从概念上讲,如果我们在队列中有以下元素:
[1, 2, 3, 4]
并且,当我们删除1
时,我们决定添加42
,队列将如下所示:
[2, 3, 4, 42]
由于队列是FIFO(先进先出)数据结构,因此这种排序是典型的。 (正如Queue
接口的文档中所述,这不是Queue
的必要条件。以PriorityQueue
为例,它按照自然顺序对元素进行排序,这样就不是FIFO 。)
以下是使用LinkedList
(Queue
)的示例,以便遍历所有元素以及在dequeing期间添加的其他元素。与上面的示例类似,在删除元素42
时添加了元素2
:
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
正如所希望的,当我们点击42
时添加的元素2
出现了。
答案 2 :(得分:8)
您可能还想查看一些更专业的类型,例如ListIterator,NavigableSet和(如果您对地图感兴趣)NavigableMap。
答案 3 :(得分:4)
实际上这很容易。想想最佳方式。 我相信最佳方式是:
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对象,并带有一个标记,标记它们不会迭代它们。
答案 4 :(得分:2)
使用ListIterator
,如下所示:
List<String> l = new ArrayList<>();
l.add("Foo");
ListIterator<String> iter = l.listIterator(l.size());
while(iter.hasPrevious()){
String prev=iter.previous();
if(true /*You condition here*/){
iter.add("Bah");
iter.add("Etc");
}
}
关键是迭代反向顺序 - 然后添加的元素出现在下一次迭代中。
答案 5 :(得分:1)
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);
}
}
}
答案 6 :(得分:1)
我知道它已经很老了。但是想到它对其他任何人都有用。最近,我遇到了一个类似的问题,我需要在迭代过程中可以修改的队列。我使用listIterator在相同的行中实现了与Avi建议的相同的方式-> Avi's Answer。看看这是否适合您的需求。
ModifyWhileIterateQueue.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ModifyWhileIterateQueue<T> {
ListIterator<T> listIterator;
int frontIndex;
List<T> list;
public ModifyWhileIterateQueue() {
frontIndex = 0;
list = new ArrayList<T>();
listIterator = list.listIterator();
}
public boolean hasUnservicedItems () {
return frontIndex < list.size();
}
public T deQueue() {
if (frontIndex >= list.size()) {
return null;
}
return list.get(frontIndex++);
}
public void enQueue(T t) {
listIterator.add(t);
}
public List<T> getUnservicedItems() {
return list.subList(frontIndex, list.size());
}
public List<T> getAllItems() {
return list;
}
}
ModifyWhileIterateQueueTest.java
@Test
public final void testModifyWhileIterate() {
ModifyWhileIterateQueue<String> queue = new ModifyWhileIterateQueue<String>();
queue.enQueue("one");
queue.enQueue("two");
queue.enQueue("three");
for (int i=0; i< queue.getAllItems().size(); i++) {
if (i==1) {
queue.enQueue("four");
}
}
assertEquals(true, queue.hasUnservicedItems());
assertEquals ("[one, two, three, four]", ""+ queue.getUnservicedItems());
assertEquals ("[one, two, three, four]", ""+queue.getAllItems());
assertEquals("one", queue.deQueue());
}
答案 7 :(得分:1)
例如,我们有两个列表:
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
答案 8 :(得分:1)
使用迭代器......不,我不这么认为。你必须将这样的东西混在一起:
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 );
哪些事情:
[foo,bar,baz,added-item-1,added-item-2]
答案 9 :(得分:0)
除了使用附加列表并调用addAll在迭代后插入新项目的解决方案(例如用户Nat的解决方案)之外,您还可以使用CopyOnWriteArrayList之类的并发集合。
“snapshot”样式迭代器方法在创建迭代器时使用对数组状态的引用。这个数组在迭代器的生命周期中永远不会改变,所以干扰是不可能的,并且保证迭代器不会抛出ConcurrentModificationException。
使用这个特殊的集合(通常用于并发访问),可以在迭代它时操纵底层列表。但是,迭代器不会反映更改。
这比其他解决方案更好吗?可能不是,我不知道Copy-On-Write方法引入的开销。
答案 10 :(得分:0)
恕我直言,更安全的方法是创建一个新的集合,迭代你的给定集合,添加新集合中的每个元素,并在新集合中添加额外的元素,最后返回新的集合。 / p>
答案 11 :(得分:0)
给定一个你想要迭代的列表List<Object>
,简单易行的方法是:
while (!list.isEmpty()){
Object obj = list.get(0);
// do whatever you need to
// possibly list.add(new Object obj1);
list.remove(0);
}
因此,您遍历列表,始终获取第一个元素然后将其删除。这样,您可以在迭代时将新元素附加到列表中。
答案 12 :(得分: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++;
}
}
答案 13 :(得分:0)
我累了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);
}
这可能会产生异常或遇到无限循环。但是,正如你所提到的那样
我很确定在我的情况下不会这样做
检查此类代码中的角落案例是您的责任。
答案 14 :(得分:0)
这就是我通常所做的事情,集合如集:
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 );
这会创建一些额外的内存(中间集的指针,但没有重复的元素发生)和额外的步骤(再次迭代更改),但通常这不是什么大问题,它可能比使用初始收集副本。
答案 15 :(得分:0)
我更喜欢在功能上处理集合而不是将它们变异。这完全避免了这种问题,以及别名问题和其他棘手的错误来源。
所以,我会像:
那样实现它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;
}
答案 16 :(得分:0)
即使我们在迭代期间无法将项添加到同一列表中,我们也可以使用Java 8的flatMap将新元素添加到流中。这可以在一个条件下完成。在此之后,可以处理添加的项目。
这是一个Java示例,它显示了如何根据条件向正在进行的流添加对象,然后使用条件处理该条件:
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]
答案 17 :(得分:-1)
在 general 中,它不安全,但对于某些收藏品可能是这样。显而易见的替代方案是使用某种for循环。但是你没有说你正在使用什么样的集合,所以这可能是也可能是不可能的。