it-swarm.com.ru

Может ли поток Java 8 работать с элементом в коллекции, а затем удалить его?

Как и все, я все еще изучаю тонкости (и люблю их) нового API Java 8 Streams. У меня есть вопрос по поводу использования потоков. Я приведу упрощенный пример.

Java Streams позволяет нам взять 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 видит что-то подобное?

60
Michael Eric Oberlin

Вы можете сделать это так:

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();
    }
}
112
Paul Boddington

В одной строке нет, но, возможно, вы могли бы использовать коллектор 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);

Это может быть более эффективным, поскольку позволяет избежать повторения набора два раза, один для фильтрации потока, а затем один для удаления соответствующих элементов.

В противном случае я думаю, что ваш подход относительно хорошо.

4
Alexis C.

Есть много подходов. При использовании myList.remove (element) необходимо переопределить equals (). Второе, что я предпочитаю, это:

allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));

Удачи и счастливого кодирования :) 

3
panayot_kulchev_bg

Что вы действительно хотите сделать, это разделить ваш набор. К сожалению, в 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)`
2
Dmitriy Yefremov

если я правильно понимаю ваш вопрос:

set = set.stream().filter(item -> {
    if (item.qualify()) {
        ((Qualifier) item).operate();
        return false;
    }
    return true;
}).collect(Collectors.toSet());
1
user_3380739

Нет, ваша реализация, вероятно, самая простая. Вы можете сделать что-то глубоко злое, изменив состояние в предикате removeIf, но, пожалуйста, не делайте этого. С другой стороны, было бы разумно на самом деле переключиться на императивную реализацию на основе итератора, которая на самом деле может быть более подходящей и эффективной для этого варианта использования.

1
Louis Wasserman

После операции они не служат никакой дальнейшей цели и должны быть удалены из первоначального набора. Код работает хорошо, но я не могу избавиться от ощущения, что в Stream есть операция, которая может сделать это для меня в одну строку.

Вы не можете удалить элементы из источника потока с потоком. Из Javadoc :

Большинство потоковых операций принимают параметры, которые описывают пользовательское поведение ..... Чтобы сохранить правильное поведение, эти поведенческие параметры:

  • должен быть не мешающим (они не изменяют источник потока); а также
  • в большинстве случаев они не должны иметь состояния (их результат не должен зависеть от состояния, которое может измениться во время выполнения потокового конвейера).
1
user7502825

Я вижу озабоченность Пола ясностью при использовании потоков, изложенную в верхнем ответе. Возможно, добавление объясняющей переменной немного проясняет намерения.

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});
0
vacant78