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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35701337/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:01:33  来源:igfitidea点击:

Java 8 lambda get and remove element from list

javalambdajava-8java-stream

提问by Marco Stramezzi

Given a list of elements, I want to get the element with a given property andremove it from the list. The best solution I found is:

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

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

Is it possible to combine get and remove in a lambda expression?

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

回答by Tunaki

The direct solution would be to invoke ifPresent(consumer)on the Optional returned by findFirst(). This consumer will be invoked when the optional is not empty. The benefit also is that it won't throw an exception if the find operation returned an empty optional, like your current code would do; instead, nothing will happen.

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

If you want to return the removed value, you can mapthe Optionalto the result of calling remove:

如果你想返回被移除的值,你可以mapOptional调用的结果remove

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

But note that the remove(Object)operation will again traverse the list to find the element to remove. If you have a list with random access, like an ArrayList, it would be better to make a Stream over the indexes of the list and find the first index matching the predicate:

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

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

With this solution, the remove(int)operation operates directly on the index.

使用此解决方案,remove(int)操作直接对索引进行操作。

回答by Bohemian

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.

p[0]将保存找到的元素或为空。

The "trick" here is circumventing the "effectively final" problem by using an arrayreference that is effectively final, but setting its first element.

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

回答by Vasily Liaskovsky

Consider using vanilla java iterators to perform the task:

考虑使用 vanilla 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;
}

Advantages:

优点

  1. It is plain and obvious.
  2. It traverses only once and only up to the matching element.
  3. You can do it on any Iterableeven without stream()support (at least those implementing remove()on their iterator).
  1. 这是简单明了的。
  2. 它只遍历一次并且只遍历匹配元素。
  3. Iterable即使没有stream()支持(至少是那些remove()在他们的迭代器上实现的),你也可以做到。

Disadvantages:

缺点

  1. You cannot do it in place as a single expression (auxiliary method or variable required)
  1. 您不能将其作为单个表达式就地执行(需要辅助方法或变量)


As for the

至于

Is it possible to combine get and remove in a lambda expression?

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

other answers clearly show that it is possible, but you should be aware of

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

  1. Search and removal may traverse the list twice
  2. ConcurrentModificationExceptionmay be thrown when removing element from the list being iterated
  1. 搜索和删除可能会遍历列表两次
  2. ConcurrentModificationException从正在迭代的列表中删除元素时可能会抛出

回答by Donald Raab

With Eclipse Collectionsyou can use detectIndexalong with remove(int)on any java.util.List.

使用Eclipse Collections,您可以在任何 java.util.List 上使用detectIndexwith remove(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);

If you use the MutableListtype from Eclipse Collections, you can call the detectIndexmethod directly on the list.

如果使用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);

Note: I am a committer for Eclipse Collections

注意:我是 Eclipse Collections 的提交者

回答by user140547

As others have suggested, this might be a use case for loops and iterables. In my opinion, this is the simplest approach. If you want to modify the list in-place, it cannot be considered "real" functional programming anyway. But you could use Collectors.partitioningBy()in order to get a new list with elements which satisfy your condition, and a new list of those which don't. Of course with this approach, if you have multiple elements satisfying the condition, all of those will be in that list and not only the first.

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

回答by Marco Stramezzi

Combining my initial idea and your answers I reached what seems to be the solution to my own question:

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

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

It lets remove the object using remove(int)that do not traverse again the list (as suggested by @Tunaki) andit lets return the removed object to the function caller.

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

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

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

Are there any important drawback in this kind of solution?

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

Edit following @Holger advice

编辑以下@Holger 建议

This should be the function I needed

这应该是我需要的功能

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

回答by uma shankar

To Remove element from the list

从列表中删除元素

objectA.removeIf(x -> conditions);

eg:

例如:

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

OUTPUT:A B C

输出:A B C

回答by asifsid88

Although the thread is quite old, still thought to provide solution - using Java8.

虽然线程很旧,但仍然认为提供解决方案 - 使用Java8.

Make the use of removeIffunction. Time complexity is O(n)

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

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

API reference: removeIf docs

API 参考:removeIf 文档

Assumption: producersProcedureActiveis a List

假设:producersProcedureActive是一个List

NOTE: With this approach you won't be able to get the hold of the deleted item.

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

回答by Toi Nguyen

Use can use filter of Java 8, and create another list if you don't want to change the old list:

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

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

回答by KimchiMan

The below logic is the solution without modifying the original list

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

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