Учитывая список элементов, я хочу получить элемент с заданным свойством и удалить его из списка. Лучшее решение, которое я нашел:
ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);
Можно ли комбинировать получение и удаление в лямбда-выражении?
java
lambda
java-8
java-stream
Марко Страмецци
источник
источник
get()
сюда! Вы не представляете, пустой он или нет. Вы вызовете исключение, если элемента не было. Вместо этого используйте один из безопасных методов, например ifPresent, orElse, orElseGet или orElseThrow.list
для которыхPredicate
истинно, или только первый (из, возможно, нуля, одного или нескольких элементов)?Ответы:
Удалить элемент из списка
например:
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
источник
removeIf
это элегантное решение для удаления элементов из коллекции, но оно не возвращает удаленный элемент.Хотя поток довольно старый, все еще думается, что решение - использование
Java8
.Используйте
removeIf
функцию. Сложность времени составляетO(n)
Справочник по API: removeIf docs
Предположение:
producersProcedureActive
этоList
ПРИМЕЧАНИЕ. При таком подходе вы не сможете получить удаленный элемент.
источник
Рассмотрите возможность использования ванильных итераторов 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()
на своем итераторе) .Недостатки :
Для
другие ответы ясно показывают, что это возможно, но вы должны знать
ConcurrentModificationException
может быть выброшено при удалении элемента из повторяемого спискаисточник
remove()
методы, которые вызывают UOE. (Конечно, не для коллекций JDK, но я считаю несправедливым сказать «работает на любом Iterable».)default
реализацияremoveIf
делает то же предположение, но, конечно же, оно определено,Collection
а неIterable
…Прямым решением было бы вызвать
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
, было бы лучше создать Stream по индексам списка и найти первый индекс, соответствующий предикату:IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int) i));
В этом решении
remove(int)
операция работает непосредственно с индексом.источник
LinkedList
вам, возможно, не следует использовать API потока, поскольку нет решения без обхода как минимум дважды. Но я не знаю ни одного реального жизненного сценария, где академическое преимущество связного списка могло бы компенсировать его фактические накладные расходы. Итак, простое решение - никогда не использоватьLinkedList
.remove(Object)
только возвращает информацию оboolean
том, был ли элемент, который нужно удалить, или нет.boxed()
вы получаетеOptionalInt
который может толькоmap
отint
кint
. В отличие отIntStream
этогоmapToObj
метода нет . С помощьюboxed()
вы получите объект,Optional<Integer>
который позволяетmap
использовать произвольный объект, то естьProducerDTO
возвращаемыйremove(int)
. Приведение отInteger
доint
необходимо для устранения неоднозначности междуremove(int)
иremove(Object)
.Используйте фильтр Java 8 и создайте другой список, если вы не хотите изменять старый список:
источник
Я уверен, что это будет непопулярный ответ, но он работает ...
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.«Уловка» здесь состоит в том, чтобы обойти проблему «фактически окончательного», используя ссылку на массив, которая фактически является окончательной, но устанавливает ее первый элемент.
источник
.orElse(null)
чтобы получитьProducerDTO
илиnull
….orElse(null)
и иметьif
, не так ли?remove()
тоже можете вызывать с помощьюorElse(null)
?if(p!=null) producersProcedureActive.remove(p);
это все еще короче, чем лямбда-выражение в вашемifPresent
вызове.С Затмением коллекциями вы можете использовать
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.
источник
Когда мы хотим получить несколько элементов из списка в новый список (фильтровать с помощью предиката) и удалить их из существующего списка , я нигде не мог найти правильный ответ.
Вот как мы можем это сделать, используя разбиение 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 По разделу этой статьи .
источник
Как предполагали другие, это может быть вариант использования циклов и итераций. На мой взгляд, это самый простой подход. Если вы хотите изменить список на месте, это все равно нельзя считать «настоящим» функциональным программированием. Но вы можете использовать
Collectors.partitioningBy()
, чтобы получить новый список с элементами, которые удовлетворяют вашему условию, и новый список тех, которые не удовлетворяют. Конечно, при таком подходе, если у вас есть несколько элементов, удовлетворяющих условию, все они будут в этом списке, а не только первым.источник
Приведенная ниже логика - это решение без изменения исходного списка.
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"]
источник
Объединив свою первоначальную идею и ваши ответы, я пришел к тому, что, кажется, является решением моего собственного вопроса:
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; }); }
источник
get
и перехватывать исключение. Это не только плохой стиль, но и может привести к снижению производительности. Чистое решение еще проще,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
задача: получить ✶ и ✶ удалить элемент из списка
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 } ) );
источник