Может ли потоки Java 8 работать с элементом в коллекции, а затем удалить его?
Как и все, я все еще изучаю тонкости (и люблю их) нового Java 8 Streams API. У меня вопрос об использовании потоков. Я приведу упрощенный пример.
Java-потоки позволяют нам использовать Collection
и использовать метод stream()
для получения потока всех его элементов. В нем есть ряд полезных методов, таких как filter()
, map()
и forEach()
, которые позволяют нам использовать лямбда-операции над содержимым.
У меня есть код, который выглядит примерно так (упрощенный):
set.stream().filter(item -> item.qualify())
.map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());
Идея состоит в том, чтобы получить отображение всех элементов в наборе, которые соответствуют определенному определителю, а затем работать через них. После операции они не служат никакой дополнительной цели и должны быть удалены из исходного набора. Код работает хорошо, но я не могу поколебать ощущение, что в Stream
есть операция, которая могла бы сделать это для меня в одной строке.
Если это в Javadocs, я могу игнорировать его.
Кто-нибудь, более знакомый с API, видит что-то подобное?
Ответы
Ответ 1
Вы можете сделать это следующим образом:
set.removeIf(item -> {
if (!item.qualify())
return false;
item.operate();
return true;
});
Если item.operate()
всегда возвращает true
, вы можете сделать это очень лаконично.
set.removeIf(item -> item.qualify() && item.operate());
Однако мне не нравятся эти подходы, так как не сразу понятно, что происходит. Лично я продолжаю использовать цикл for
и Iterator
для этого.
for (Iterator<Item> i = set.iterator(); i.hasNext();) {
Item item = i.next();
if (item.qualify()) {
item.operate();
i.remove();
}
}
Ответ 2
В одной строке нет, но, возможно, вы можете использовать коллекцию partitioningBy
:
Map<Boolean, Set<Item>> map =
set.stream()
.collect(partitioningBy(Item::qualify, toSet()));
map.get(true).forEach(i -> ((Qualifier)i).operate());
set = map.get(false);
Он может быть более эффективным, так как он избегает итерации набора два раза, один для фильтрации потока, а затем один для удаления соответствующих элементов.
В противном случае я думаю, что ваш подход относительно хорош.
Ответ 3
Что вы действительно хотите сделать, так это разбить ваш набор. К сожалению, в Java 8 разбиение на разделы возможно только с помощью метода "собрать" терминала. Вы получите что-то вроде этого:
// test data set
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5);
// predicate separating even and odd numbers
Predicate<Integer> evenNumber = n -> n % 2 == 0;
// initial set partitioned by the predicate
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber));
// print even numbers
partitioned.get(true).forEach(System.out::println);
// do something else with the rest of the set (odd numbers)
doSomethingElse(partitioned.get(false))
Обновлено:
Scala версия кода выше
val set = Set(1, 2, 3, 4, 5)
val partitioned = set.partition(_ % 2 == 0)
partitioned._1.foreach(println)
doSomethingElse(partitioned._2)`
Ответ 4
Нет, ваша реализация, вероятно, самая простая. Вы можете сделать что-то глубоко злое, изменив состояние в предикате removeIf
, но, пожалуйста, не делайте этого. С другой стороны, было бы разумно фактически перейти на настоящую императивную реализацию, основанную на итераторе, которая может быть более подходящей и эффективной для этого случая использования.
Ответ 5
если я правильно понял ваш вопрос:
set = set.stream().filter(item -> {
if (item.qualify()) {
((Qualifier) item).operate();
return false;
}
return true;
}).collect(Collectors.toSet());
Ответ 6
После операции они не служат никакой дополнительной цели и должны быть удалены из исходного набора. Код работает хорошо, но я не могу поколебать ощущение, что в Stream есть операция, которая может сделать это для меня в одной строке.
Вы не можете удалить элементы из источника потока с потоком. Из Javadoc:
Большинство операций потока принимают параметры, описывающие поведение, заданное пользователем..... Чтобы сохранить правильное поведение, эти поведенческие параметры:
- должен быть неинтерферирующим (они не изменяют источник потока); и
- в большинстве случаев должны быть безстоящими (их результат не должен зависеть от какого-либо состояния, которое может измениться во время выполнения конвейера потока).
Ответ 7
Я вижу озабоченность ясности Пола при использовании потоков, изложенных в верхнем ответе. Возможно, добавление объясняющей переменной немного разъясняет намерения.
set.removeIf(item -> {
boolean removeItem=item.qualify();
if (removeItem){
item.operate();
}
return removeItem;
});
Ответ 8
user.getSongs()
.stream()
.filter(song -> song.getSinger().getId() != singerId) // Only those songs where singer ID doesn't match
.forEach(song -> user.getSongs().remove(song)); // Then remove them