Java 8 lambda получить и удалить элемент из списка

Учитывая список элементов, я хочу, чтобы элемент с заданным свойством и удалял его из списка. Лучшее решение, которое я нашел, это:

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

Можно ли комбинировать get и remove в выражении лямбда?

Ответы

Ответ 1

Удалить элемент из списка

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

ВЫВОД: В С

Ответ 2

Хотя поток довольно старый, все же считается, что он предлагает решение с использованием Java8.

Используйте функцию removeIf. Временная сложность O(n)

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

Справочник по API: removeIf docs

Предположение: producersProcedureActive является List

ПРИМЕЧАНИЕ. При таком подходе вы не сможете удержать удаленный элемент.

Ответ 3

Рассмотрите возможность использования итераторов 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;
}

<сильные > Преимущества:

  • Это ясно и очевидно.
  • Он перемещается только один раз и только до соответствующего элемента.
  • Вы можете сделать это на любой Iterable даже без поддержки stream() (по крайней мере, тех, кто реализует remove() на своем итераторе).

Недостатки

  • Вы не можете сделать это как единое выражение (требуется вспомогательный метод или переменная)

Что касается

Можно ли комбинировать get и remove в выражении лямбда?

другие ответы ясно показывают, что это возможно, но вы должны знать

  • Поиск и удаление могут проходить дважды по списку
  • ConcurrentModificationException может быть сброшен при удалении элемента из повторяющегося списка

Ответ 4

Прямым решением было бы вызвать ifPresent(consumer) для Optional, возвращаемого findFirst(). Этот потребитель будет вызываться, когда необязательный параметр не пуст. Преимущество также состоит в том, что оно не будет выдавать исключение, если операция поиска вернула пустой необязательный параметр, как это сделал бы ваш текущий код; вместо этого ничего не произойдет.

Если вы хотите вернуть удаленное значение, вы можете map Optional с результатом вызова remove:

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

Но обратите внимание, что операция remove(Object) снова будет проходить по списку, чтобы найти удаляемый элемент. Если у вас есть список с произвольным доступом, такой как ArrayList, было бы лучше создать поток поверх индексов списка и найти первый индекс, соответствующий предикату:

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

В этом решении операция remove(int) работает непосредственно с индексом.

Ответ 5

Use может использовать фильтр Java 8 и создать другой список, если вы не хотите изменять старый список:

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

Ответ 6

Я уверен, что это будет непопулярный ответ, но он работает...

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

p[0] будет либо удерживать найденный элемент, либо быть нулевым.

"Трюк" здесь обходит "эффективно финальную" проблему, используя ссылку на массив, которая является фактически окончательной, но устанавливает ее первый элемент.

Ответ 7

С Eclipse Collections вы можете использовать detectIndex вместе с remove(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);

Если вы используете тип MutableList из Eclipse 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

Ответ 8

Как и другие, это может быть прецедентом для циклов и итераций. На мой взгляд, это самый простой подход. Если вы хотите изменить список на месте, он не может считаться "реальным" функциональным программированием в любом случае. Но вы можете использовать Collectors.partitioningBy(), чтобы получить новый список с элементами, которые удовлетворяют вашему условию, и новый список тех, которые этого не делают. Конечно, при таком подходе, если у вас есть несколько элементов, удовлетворяющих условию, все они будут в этом списке, а не только в первом.

Ответ 9

Приведенная ниже логика является решением без изменения исходного списка

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

Ответ 10

Объединив мою первоначальную идею и ваши ответы, я достиг того, что кажется решением на мой вопрос:

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

Ответ 11

Когда мы хотим получить несколько элементов из списка в новый список (отфильтровать с помощью предиката) и удалить их из существующего списка, я не мог найти правильный ответ нигде.

Вот как мы можем сделать это, используя 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.partitioning В разделе этой статьи.

Ответ 12

Задача: получить и удалить элемент из списка

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