Найти элемент в списке и изменить его с помощью stream ()

#java #java-8 #java-stream

#java #java-8 #java-stream

Вопрос:

Можно ли найти элемент в a List , изменить его или выбросить Exception , если элемент не был найден, используя Java 8 stream?

Другими словами, я хотел бы переписать следующий код с помощью stream . Лучшее, что я мог получить, это изменить значение элемента, но нет способа узнать, был ли найден / изменен элемент.

 boolean isFound = false;
for (MyItem item : myList) {
    if (item.getValue() > 10) {
        item.setAnotherValue(5);
        isFound = true;
    }
}

if (!isFound) {
    throw new ElementNotFoundException("Element 10 wasn't found");
}
 

Ответ №1:

Если ваша цель — найти только один элемент, вы могли бы сделать

 MyItem item = l.stream()
                .filter(x -> x.getValue() > 10)
                .findAny()  // here we get an Optional
                .orElseThrow(() -> new RuntimeException("Element 10 wasn't found"));
        item.setAnotherValue(4);
 

В Java 9, используя ifPresentOrElse , это можно несколько упростить (к сожалению, синтаксис ()->{throw new RuntimeException();} также немного неуклюж, но AFAIK его нельзя упростить):

 l.stream()
    .filter(x -> x.getValue() > 10)
    .findAny() // here we get an Optional
    .ifPresentOrElse(x->x.setAnotherValue(5),
            ()->{throw new RuntimeException();});
 

Если вы хотите сделать это для всех элементов, вы можете попробовать что-то подобное. Но поскольку потоки Java 8 не предназначены для работы с побочными эффектами, это не совсем чистый подход:

 AtomicBoolean b = new AtomicBoolean(false);
l.stream()
        .filter(x -> x.getValue() > 10)
        .forEach(x->{
            x.setAnotherValue(5);
            b.set(true);
        });
if (b.get()){
   throw new RuntimeException();
}
 

Конечно, вы также можете просто собрать элементы в список, а затем выполнить свои операции. Но я не уверен, что это какое-то улучшение по сравнению с простым циклом for, с которого вы начали…

Ну, если forEach бы возвращался a long , который представляет количество элементов, для которых он был вызван, это было бы проще…

Комментарии:

1. Это AtomicBoolean решение выглядит ужасно. Это скорее задача для collect : boolean hadMatches = l.stream() .filter(x -> x.getValue() > 10) .collect(()->new boolean[1], (b,x)->{x.setAnotherValue(5);b[0]=true;}, (b1,b2)->b1[0]|=b2[0])[0];

2. @Holger: ну, я думаю, ни одно из решений не является особенно элегантным. Или вы думаете, что при использовании этого с параллельными потоками может возникнуть проблема с конфликтом AtomicBoolean ?

3. В этом случае это не конфликт (поскольку все потребители только пишут, а не делают cas ), а наличие барьера памяти между обработкой каждого элемента, препятствующего оптимизации точек доступа, что имеет место даже при последовательном выполнении, если только оптимизатор не выяснит, что это AtomicBoolean чисто локально. collect Операция может выглядеть не очень элегантно, но она близка к тому, что mapping(x->{x.setAnotherValue(5); return true;}, reducing(false, Boolean::logicalOr)) нужно делать. О, подождите, это может быть альтернативой…

4. Хорошо, тогда мы также можем использовать .map(x->{x.setAnotherValue(5); return true;}) .reduce(false, Boolean::logicalOr); . В любом случае действие, связанное с побочным эффектом, должно быть где-то размещено…