Подтвердить что ты не робот

Может ли потоки 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, видит что-то подобное?

4b9b3361

Ответ 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