Java 如何在foreach方法中从流中删除对象?

Java 如何在foreach方法中从流中删除对象?,java,arrays,lambda,java-8,Java,Arrays,Lambda,Java 8,我必须使用数组:arrA和arrBarrA和arrB是不同类型的对象列表,并且add函数将对象A转换为对象B。我想将arrA中的每个对象添加到arrB,并从arrA中删除该对象。我正在尝试通过流来实现这一点: arrA.stream().foreach(c -> {arrB.add(c); arrA.remove(c);}); 当我执行此操作时,发生了两件事: 并非所有对象都从arrA传递到arrB 经过几次迭代后,抛出空指针异常 这是因为每次remove()调用后数组的长度都会减少,迭

我必须使用数组:
arrA
arrB
arrA
arrB
是不同类型的对象列表,并且
add
函数将对象
A
转换为对象
B
。我想将arrA中的每个对象添加到arrB,并从arrA中删除该对象。我正在尝试通过流来实现这一点:

arrA.stream().foreach(c -> {arrB.add(c); arrA.remove(c);});
当我执行此操作时,发生了两件事:

  • 并非所有对象都从arrA传递到arrB
  • 经过几次迭代后,抛出空指针异常
  • 这是因为每次
    remove()
    调用后数组的长度都会减少,迭代次数的计数器也会增加(只有奇数索引下的对象才会传递给
    arrB

    现在我可以通过在一个流调用中复制数组,然后在第二个流调用中删除对象来解决这个问题,但这对我来说似乎并不正确

    这个问题的正确解决方案是什么

    编辑。 其他信息: 在实际实现中,如果以前进行过筛选,则此列表

    arrA.stream().filter(some condition).foreach(c -> {arrB.add(c); arrA.remove(c);});
    

    将满足不同条件的元素添加到不同的列表(
    arrC,arrD
    等)需要多次调用,但每个对象只能在一个列表上

    我认为在迭代时不能从arrA中删除

    您可以通过将其包装在新的ArrayList()中来解决此问题

    新的ArrayList(arrA.stream().foreach(c->{arrB.add(c);arrA.remove(c);})

    我猜这是因为每次remove()调用后数组的长度都会减少,而迭代的计数器会增加

    对。for-each循环与普通的for循环一样,但更易于写入和读取。你可以把它看作是句法上的糖。在内部,它将使用迭代器或数组索引。streams的
    forEach
    方法是它的一个更奇特的版本,它允许并行执行和功能编码风格

    与任何索引循环一样,在循环时删除元素会中断循环。考虑具有指数0, 1和2的三个元素。当您在第一次迭代中删除元素0时,列表项将向上移动一个元素,在下一次迭代中,您将有元素0(以前为1)和1(以前为2)。循环变量现在指向1,因此它跳过下一项。当它到达索引2时,您正在处理的循环只剩下一个项(您删除了两个),这会抛出一个错误,因为索引超出了范围

    可能的解决办法:

    • 使用
      List
      方法克隆和清除列表
    • 如果您真的需要对每个项目调用方法,请使用两个循环

    流被设计为以更有效的方式使用,最好将您的集合视为不可变的

    非溪流方式为:

    arrB.addAll(arrA);
    arrA.clear();
    
    但是,您可能正在使用流,以便可以过滤输入,使其更像:

    arrB.addAll(arrA.stream().filter(x -> whatever).toList())
    
    然后从arrA中删除(感谢@Holgar的评论)

    如果您的谓词很昂贵,那么您可以:

    Map<Boolean, XXX> lists = arrA.stream()
      .collect(Collectors.partitioningBy(x -> whatever));
    arrA = lists.get(false);
    arrB = lists.get(true);
    
    Map list=arrA.stream()
    .collect(收集器.partitionby(x->whatever));
    arrA=lists.get(false);
    arrB=lists.get(true);
    
    或列出所做的更改:

    List<XXX> toMove = arrA.stream().filter(x->whatever).toList();
    arrA.removeAll(toMove);
    arrB.addAll(toMove);
    
    List toMove=arrA.stream().filter(x->whatever.toList();
    arrA.removeAll(toMove);
    arrB.addAll(toMove);
    
    正如其他人所提到的,这在使用
    foreach
    时是不可能的,因为使用
    for(A:arrA)
    循环不可能删除元素

    在我看来,最干净的解决方案是在使用迭代器时使用纯for
    ——迭代器允许您在迭代时删除元素(只要集合支持)

    Iterator it=arrA.Iterator()
    while(it.hasNext()){
    A=it.next();
    如果(!检查(a))
    继续;
    b.加入(a);
    it.remove();
    }
    

    这还可以避免复制/克隆
    arrA

    您只需执行Collections.addAll即可。然后当这一切结束。只需在arrA上调用clear()。

    是否需要逐个添加和删除对象?你只需一个方法调用就可以克隆整个列表并清除另一个列表,你知道吗?
    arrA.remove的意义何在?它最终将变成空的,这是一个不变的。添加了编辑并简要解释了为什么我需要逐个删除元素短而脏:
    listA.removeIf(c->someCondition&&listB.add(c))
    有效,因为
    列表。add
    始终返回
    true
    。可能重复您所说的增强for循环,而不是流的
    forEach
    方法。对。添加了一个关于这一点的注释。“包装”实际上为您最后的“更改列表”创建了
    arrA
    +1的克隆。这似乎与OP想要的最接近。其他一切似乎都不能直接满足OP的要求,或者效率较低。@Martin Nyolt:实际上,没有什么比“更改列表”方法更糟糕的了。
    removeAll
    的时间复杂度将为n×m,如果所有元素都与过滤器匹配,则时间复杂度将达到二次。
    partitioning by
    方法值得我+1。作为补充,如果
    arrA
    是可变的(正如最初的
    remove
    方法所建议的那样),
    arrA=arrA.stream().filter(x->whatever).toList()
    可以替换为
    arrA.removeIf(x->!whatever)
    .
    removeAll
    是n×m:很好,我没有想到这一点。使其线性化当然是可能的,因为
    toMove
    arrA
    中的顺序是相同的-但与其他方法相比,这可能不值得付出努力。请参阅OPs更新。这不是他想要的(只删除选定的元素)。我更喜欢这种解决方案。我相信溪流是好的,只要我不需要为了实现我所需要的而与之斗争。
    List<XXX> toMove = arrA.stream().filter(x->whatever).toList();
    arrA.removeAll(toMove);
    arrB.addAll(toMove);
    
    Iterator<A> it = arrA.iterator()
    while (it.hasNext()) {
        A a = it.next();
        if (!check(a))
            continue;
        arrB.add(a);
        it.remove();
    }