Java8 lambda 从 list 中获取和删除元素

给定一个元素列表,我希望获取具有给定属性的元素 还有,并将其从列表中删除。我找到的最佳解决方案是:

ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);

是否可以在 lambda 表达式中组合 get 和 delete?

250120 次浏览

直接的解决方案是在 findFirst()返回的可选项上调用 ifPresent(consumer)。当可选项不为空时,将调用此使用者。这样做的好处还在于,如果 find 操作返回一个空的可选项,它不会抛出异常,就像当前代码所做的那样; 相反,什么也不会发生。

如果要返回移除的值,可以将 Optional返回到调用 remove的结果:

producersProcedureActive.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.map(p -> {
producersProcedureActive.remove(p);
return p;
});

但是请注意,remove(Object)操作将再次遍历列表以查找要删除的元素。如果您有一个具有随机访问权限的列表,比如 ArrayList,那么最好在列表的索引上创建一个 Stream,并找到与谓词匹配的第一个索引:

IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int) i));

有了这个解决方案,remove(int)操作就可以直接对索引进行操作。

I'm sure this will be an unpopular answer, but it works...

ProducerDTO[] p = new ProducerDTO[1];
producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}

p[0] will either hold the found element or be null.

这里的“技巧”是通过使用一个实际上是 final 的 array引用,但是设置它的第一个元素来规避“有效的 final”问题。

考虑使用普通的 java 迭代器来执行这个任务:

public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) {
T value = null;
for (Iterator<? extends T> it = collection.iterator(); it.hasNext();)
if (test.test(value = it.next())) {
it.remove();
return value;
}
return null;
}

优点 :

  1. 这是显而易见的。
  2. 它只遍历一次,并且只到达匹配的元素。
  3. 即使没有 stream()支持 (至少那些在迭代器上实现 remove()的),也可以在任何 Iterable上进行。

缺点 :

  1. 不能将其作为单个表达式(需要辅助方法或变量)执行

至于

是否可以在 lambda 表达式中组合 get 和 delete?

其他答案清楚显示这是可能的,但你应该知道

  1. 搜索和删除可以遍历列表两次
  2. 当从正在迭代的列表中删除元素时,可能会引发 ConcurrentModificationException

使用 Eclipse Collections,您可以在任何 java.util.List 上使用 detectIndexremove(int)

List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = Iterate.detectIndex(integers, i -> i > 2);
if (index > -1) {
integers.remove(index);
}


Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

如果使用 Eclipse 集合中的 MutableList类型,则可以直接在列表中调用 detectIndex方法。

MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5);
int index = integers.detectIndex(i -> i > 2);
if (index > -1) {
integers.remove(index);
}


Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);

注意: 我是 Eclipse 集合的提交者

正如其他人所建议的,这可能是循环和迭代的用例。在我看来,这是最简单的方法。如果希望就地修改列表,则不能将其视为“真正的”函数式编程。但是您可以使用 Collectors.partitioningBy()来获得一个包含满足条件的元素的新列表,以及一个包含不满足条件的元素的新列表。当然,使用这种方法,如果有多个元素满足条件,那么所有这些元素都将在该列表中,而不仅仅是第一个元素。

把我最初的想法和你的回答结合起来,我找到了似乎可以解决问题的方法 回答我自己的问题:

public ProducerDTO findAndRemove(String pod) {
ProducerDTO p = null;
try {
p = IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int)i))
.get();
logger.debug(p);
} catch (NoSuchElementException e) {
logger.error("No producer found with POD [" + pod + "]");
}
return p;
}

它允许使用不再遍历 列表(如@Tunaki 所建议的) 还有,它允许将移除的对象返回到 the function caller.

I read your answers that suggest me to choose safe methods like ifPresent instead of get but I do not find a way to use them in this scenario.

这种解决方案有什么重要的缺点吗?

按照@Holger 的建议编辑

这应该就是我需要的功能

public ProducerDTO findAndRemove(String pod) {
return IntStream.range(0, producersProcedureActive.size())
.filter(i -> producersProcedureActive.get(i).getPod().equals(pod))
.boxed()
.findFirst()
.map(i -> producersProcedureActive.remove((int)i))
.orElseGet(() -> {
logger.error("No producer found with POD [" + pod + "]");
return null;
});
}

To Remove element from the list

objectA.removeIf(x -> conditions);

例如:

objectA.removeIf(x -> blockedWorkerIds.contains(x));


List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");


List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");


str1.removeIf(x -> str2.contains(x));


str1.forEach(System.out::println);

产出: A B C

虽然这个线程已经很老了,但是仍然认为可以提供解决方案——使用 Java8

利用 removeIf函数,时间复杂度为 O(n)

producersProcedureActive.removeIf(producer -> producer.getPod().equals(pod));

API 参考资料: 删除文件

假设: producersProcedureActiveList

注意: 使用这种方法,您将无法获得已删除的项目。

Use 可以使用 Java8的 filter,如果不想更改旧的列表,可以创建另一个列表:

List<ProducerDTO> result = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.collect(Collectors.toList());

下面的逻辑是不修改原始列表的解决方案

List<String> str1 = new ArrayList<String>();
str1.add("A");
str1.add("B");
str1.add("C");
str1.add("D");


List<String> str2 = new ArrayList<String>();
str2.add("D");
str2.add("E");


List<String> str3 = str1.stream()
.filter(item -> !str2.contains(item))
.collect(Collectors.toList());


str1 // ["A", "B", "C", "D"]
str2 // ["D", "E"]
str3 // ["A", "B", "C"]

When we want to 将 List 中的多个元素放入一个新列表(使用谓词进行筛选) ,并将它们从现有列表中删除, I could not find a proper answer anywhere.

下面是我们如何使用 JavaStreamingAPI 分区来实现这一点。

Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive
.stream()
.collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod)));


// get two new lists
List<ProducerDTO> matching = classifiedElements.get(true);
List<ProducerDTO> nonMatching = classifiedElements.get(false);


// OR get non-matching elements to the existing list
producersProcedureActive = classifiedElements.get(false);

通过这种方式,可以有效地从原始列表中删除经过筛选的元素,并将它们添加到新列表中。

Refer the 5.2. Collectors.PartitioningBy section of 这篇文章.

任务是: 从 list 中移除元素

p.stream().collect( Collectors.collectingAndThen( Collector.of(
ArrayDeque::new,
(a, producer) -> {
if( producer.getPod().equals( pod ) )
a.addLast( producer );
},
(a1, a2) -> {
return( a1 );
},
rslt -> rslt.pollFirst()
),
(e) -> {
if( e != null )
p.remove( e );  // remove
return( e );    // get
} ) );
resumoRemessaPorInstrucoes.removeIf(item ->
item.getTipoOcorrenciaRegistro() == TipoOcorrenciaRegistroRemessa.PEDIDO_PROTESTO.getNome() ||
item.getTipoOcorrenciaRegistro() == TipoOcorrenciaRegistroRemessa.SUSTAR_PROTESTO_BAIXAR_TITULO.getNome());

上述情况的一种变体:

    import static java.util.function.Predicate.not;


final Optional<MyItem> myItem = originalCollection.stream().filter(myPredicate(someInfo)).findFirst();
final List<MyItem> myOtherItems = originalCollection.stream().filter(not(myPredicate(someInfo))).toList();


private Predicate<MyItem> myPredicate(Object someInfo) {
return myItem -> myItem.someField() == someInfo;
}