Java 8 Lambda从列表中获取和删除元素


88

鉴于元素的列表,我想用一个给定的属性来获取元素,并从列表中删除。我发现的最佳解决方案是:

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

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


9
这似乎确实是一个经典案例,说明何时仅使用循环和迭代器。
克莱里斯-cautiouslyoptimistic-

1
@chrylis我非常不同意;)我们已经习惯了命令式编程,以任何其他方式听起来都太过奇特了。想象一下现实是否恰恰相反:我们非常熟悉函数式编程,并且向Java添加了新的命令式范式。您是否会说这是流,谓词和可选内容的经典案例?
fps

9
不要get()在这里打电话!您不知道它是否为空。如果元素不存在,则将引发异常。而是使用安全的方法之一,例如ifPresent,orElse,orElseGet或orElseThrow。
Brian Goetz

@FedericoPeraltaSchaffner可能是口味问题。但是我也建议不要将两者结合。当我看到一些代码使用流获取值时,我通常认为流中的操作没有副作用。混合删除元素可能会导致代码误导读者。
马提亚斯·威默

只是为了澄清:你要删除的所有元素在list为其Predicate是真实的或只有第(一可能是零,一个或多个元素的)?
Kedar Mhaswade '16

Answers:


148

从列表中删除元素

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


我建议这是最好的答案
谢尔盖·包克

1
这很整洁。如果没有为您自己的对象完成此操作,则可能需要实现equals / hashCode。(在此示例中,使用了默认情况下具有它们的字符串)。
bram000 '18年

15
恕我直言,答案没有考虑“获取”部分:这removeIf是一种从集合中删除元素的优雅解决方案,但它不返回已删除的元素。
Marco Stramezzi '18年

从Java ArrayList排除对象的一种非常简单的模式。非常感谢。对我来说很好。
MarceloRebouças19年

2
这不能回答问题。要求是从列表中删除元素,然后将删除的项目添加到新列表中。
Shanika Ediriweera,

34

尽管线程很旧,但仍认为可以提供解决方案-using Java8

利用removeIf功能。时间复杂度为O(n)

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

API参考:removeIf docs

假设:producersProcedureActiveList

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


除了从列表中删除该元素外,OP还希望引用该元素。
eee

@eee:非常感谢您指出这一点。我错过了OP最初的问题。
asifsid88

只是要注意,这将删除所有与条件匹配的项目。但是OP似乎只需要删除第一项(OP使用了findFirst())
nantitv

17

考虑使用香草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. Iterable甚至可以在没有任何stream()支持的情况下(至少是remove()在其迭代器上实现的)进行操作

缺点

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

至于

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

其他答案清楚地表明这是可能的,但是您应该意识到

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

4
我喜欢这种解决方案,但是请注意,它有一个您遗漏的严重缺点:许多Iterable实现都有remove()抛出UOE的方法。(当然,不是JDK集合中的那些,但是我认为说“在任何Iterable上工作”是不公平的。)
Brian Goetz

我认为,我们可以假设,如果一个元素可以被去除一般,它可以被删除通过迭代器
瓦西里Liaskovsky

5
您可以假设,但是查看了数百个迭代器实现之后,这将是一个错误的假设。(我仍然喜欢这种方法;您只是卖得过多。)
Brian Goetz

2
@Brian Goetz:的default实现采用removeIf相同的假设,但是,当然,它的定义Collection不是,而是Iterable
Holger

13

直接的解决方案是ifPresent(consumer)对所返回的Optional进行调用findFirst()。当可选参数不为空时,将调用此使用者。好处还在于,如果find操作返回空的可选内容(如您当前的代码那样),它将不会引发异常。相反,什么也不会发生。

如果你想返回被移除的值,你可以mapOptional调用的结果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)操作直接在索引上进行。


3
对于链接列表,这是病理性的。
克莱里斯-cautiouslyoptimistic-

1
@chrylis索引解决方案的确是。根据列表的实现,一个将比另一个更喜欢一个。进行了小的编辑。
Tunaki '16

1
@chrylis:如果是a,LinkedList您可能不应该使用流API,因为没有解决方案必须至少遍历两次。但是我不知道在现实生活中,链表的学术优势可以弥补其实际开销。因此,简单的解决方案是永远不要使用LinkedList
Holger

2
如此之多的编辑……现在,第一个解决方案不提供已删除的元素,因为它remove(Object)仅返回一个boolean告诉是否要删除的元素。
霍尔格

3
@Marco Stramezzi:不幸的是,解释它的评论已被删除。没有boxed()OptionalInt,那只map能从intint。不像IntStream,没有mapToObj方法。使用boxed(),您将获得一个Optional<Integer>允许map任意对象的,即ProducerDTO返回remove(int)。要消除和之间的歧义,必须使用从Integerint的转换。remove(int)remove(Object)
Holger

9

Use可以使用Java 8的过滤器,如果您不想更改旧列表,则可以创建另一个列表:

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

5

我敢肯定这将是一个不受欢迎的答案,但是它的确有效...

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

p[0] 将包含找到的元素或为null。

这里的“技巧”是通过使用有效最终但设置其第一个元素的数组引用来规避“有效最终”问题。


1
这这种情况下,它并不坏,但不超过的可能性,只是通话的改进.orElse(null),以获得ProducerDTOnull...
霍尔格

在这种情况下,拥有.orElse(null)和拥有一个会更容易if,不是吗?
Tunaki '16

@Holger,但是如何使用来调用remove()orElse(null)
波希米亚

1
只是使用结果。if(p!=null) producersProcedureActive.remove(p);仍然比您ifPresent通话中的lambda表达式短。
Holger

@holger我将问题的目标解释为避免多条语句-即单行解决方案
波西米亚风格

4

通过Eclipse Collections,您可以将其detectIndexremove(int)任何java.util.List一起使用。

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);

如果使用MutableListEclipse Collections中的类型,则可以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 Collections的提交者


2

当我们希望将一个列表中的多个元素放入一个新列表(使用谓词进行过滤)并将其从现有列表中删除时,我在任何地方都找不到合适的答案。

这是我们使用Java Streaming API分区的方法。

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);

这样,您可以有效地从原始列表中删除过滤的元素,并将其添加到新列表中。

请参阅5.2。本文的Collectors.partitioningBy部分。


1

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


更好地过滤流并将结果收集到新列表中
fps 2016年

1

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

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"]

0

结合最初的想法和您的回答,我得出了似乎可以解决我自己的问题的方法:

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;
}

它允许使用remove(int)不再次遍历列表的对象来删除该对象(如@Tunaki所建议)并允许将删除的对象返回给函数调用者。

我看了你的答案是建议我选择安全的方法,例如ifPresent,而不是get但是在这种情况下我找不到使用它们的方法。

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

编辑以下@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; 
            });
}

2
您不应使用get并捕获异常。这不仅是糟糕的风格,而且还可能导致性能下降。干净的解决方案甚至更简单return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
Holger

0

任务是:获取✶✶从列表中删除元素

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
  } ) );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.