Могу ли я изменять / обновлять объекты в потоках Java8? Например, List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Могу ли я изменять / обновлять объекты в потоках Java8? Например, List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Ответы:
Да, вы можете изменять состояние объектов внутри вашего потока, но чаще всего вам следует избегать изменения состояния источника потока. Из раздела о невмешательстве в документацию потокового пакета мы можем прочитать, что:
Для большинства источников данных предотвращение помех означает гарантию того, что источник данных вообще не будет изменен во время выполнения потокового конвейера. Заметным исключением из этого правила являются потоки, источниками которых являются параллельные коллекции, специально разработанные для обработки одновременных изменений. Источники параллельного потока - это те, чьи
Spliterator
отчеты имеютCONCURRENT
характеристику.
Так что это нормально
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
но это в большинстве случаев не
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
и может вызывать ConcurrentModificationException
или даже другие неожиданные исключения, такие как NPE:
List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());
list.stream()
.filter(i -> i > 5)
.forEach(i -> list.remove(i)); //throws NullPointerException
Iterator
что предотвращает изменение списка (удаление / добавление новых элементов) во время итерации, и оба потока и циклы for-each используют его. Вы можете попробовать использовать простой цикл, например, в for(int i=0; ..; ..)
котором нет этой проблемы (но это не остановит вас, когда другой поток изменит ваш список). Вы можете также использовать такие методы , как list.removeAll(Collection)
, list.removeIf(Predicate)
. Также у java.util.Collections
класса есть несколько методов, которые могут быть полезны, например addAll(CollectionOfNewElements,list)
.
filter()
Функциональный способ imho будет:
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTestRun {
public static void main(String[] args) {
List<String> lines = Arrays.asList("a", "b", "c");
System.out.println(lines); // [a, b, c]
Predicate<? super String> predicate = value -> "b".equals(value);
lines = lines.stream().filter(predicate.negate()).collect(toList());
System.out.println(lines); // [a, c]
}
}
В этом решении исходный список не изменяется, но должен содержать ожидаемый результат в новом списке, который доступен под той же переменной, что и старый.
Для структурной модификации на источнике потока, как и Pshemo отметил в своем ответе, одно решения , чтобы создать новый экземпляр , Collection
как ArrayList
с предметами внутри основного списка; перебирать новый список и выполнять операции с основным списком.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
Чтобы избавиться от ConcurrentModificationException, используйте CopyOnWriteArrayList
List<Something>
- вы понятия не имеете, является ли это CopyOnWriteArrayList. Так что это больше похоже на посредственный комментарий, но не на настоящий ответ.
Вместо того, чтобы создавать странные вещи, вы можете просто filter()
и потом получить map()
свой результат.
Это гораздо более читабельно и надежно. Потоки сделают это всего за один цикл.
Да, вы можете изменить или обновить значения объектов в списке в вашем случае аналогичным образом:
users.stream().forEach(u -> u.setProperty("some_value"))
Однако приведенный выше оператор обновит исходные объекты. Что в большинстве случаев может быть неприемлемым.
К счастью, у нас есть другой способ:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
Что возвращает обновленный список обратно, не мешая старому.
Как уже упоминалось ранее, вы не можете изменять исходный список, но можете транслировать, изменять и собирать элементы в новый список. Вот простой пример изменения строкового элемента.
public class StreamTest {
@Test
public void replaceInsideStream() {
List<String> list = Arrays.asList("test1", "test2_attr", "test3");
List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
System.out.println("Output: " + output); // Output: [test1, test2, test3]
}
}
Вы можете использовать removeIf
для условного удаления данных из списка.
Например: - Если вы хотите удалить все четные числа из списка, вы можете сделать это следующим образом.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
Это может быть немного поздно. Но вот один из вариантов использования. Это для подсчета количества файлов.
Создайте указатель на память (в данном случае новый объект) и измените свойство объекта. Поток Java 8 не позволяет изменять сам указатель, и, следовательно, если вы объявите просто count как переменную и попытаетесь увеличить в потоке, он никогда не будет работать и в первую очередь вызовет исключение компилятора
Path path = Paths.get("/Users/XXXX/static/test.txt");
Count c = new Count();
c.setCount(0);
Files.lines(path).forEach(item -> {
c.setCount(c.getCount()+1);
System.out.println(item);});
System.out.println("line count,"+c);
public static class Count{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Count [count=" + count + "]";
}
}