Чи можна в потоках 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(int i=0; ..; ..)
якому не має цієї проблеми (але не зупинить вас, коли інший потік змінить ваш список). Ви можете також використовувати такі методи , як list.removeAll(Collection)
, list.removeIf(Predicate)
. Також у java.util.Collections
класу є кілька методів, які можуть бути корисними addAll(CollectionOfNewElements,list)
.
filter()
Функціональний спосіб буде таким:
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]
}
}
У цьому рішенні оригінальний список не змінено, але повинен містити очікуваний результат у новому списку, який доступний під тією ж змінною, що і старий
Для структурної модифікації джерела потоку, як згадав Пшемо у своїй відповіді, одним із рішень є створення нового екземпляра 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);
Це може бути трохи пізно. Але ось одне з використання. Це для отримання підрахунку кількості файлів.
Створіть вказівник на пам’ять (у цьому випадку новий obj) і змініть властивість об’єкта. Потік 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 + "]";
}
}