Чи можна в потоках 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 + "]";
}
}