Czy w strumieniach Java8 mogę modyfikować / aktualizować obiekty? Np. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Czy w strumieniach Java8 mogę modyfikować / aktualizować obiekty? Np. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Odpowiedzi:
Tak, możesz modyfikować stan obiektów w swoim strumieniu, ale najczęściej powinieneś unikać modyfikowania stanu źródła strumienia. Z sekcji dotyczącej braku zakłóceń w dokumentacji pakietu strumieniowego możemy przeczytać, że:
W przypadku większości źródeł danych zapobieganie zakłóceniom oznacza zapewnienie, że źródło danych nie jest w ogóle modyfikowane podczas wykonywania potoku strumienia. Godnym uwagi wyjątkiem od tej reguły są strumienie, których źródła są kolekcjami współbieżnymi, które są specjalnie zaprojektowane do obsługi współbieżnych modyfikacji. Współbieżne źródła strumieni to te, których
Spliterator
raporty mająCONCURRENT
charakterystykę.
Więc to jest OK
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
ale w większości przypadków tak nie jest
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
i może rzucać ConcurrentModificationException
lub nawet inne nieoczekiwane wyjątki, takie jak 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
co zapobiega modyfikowaniu listy (usuwaniu / dodawaniu nowych elementów) podczas iteracji i używają jej zarówno strumienie, jak i pętle for-each. Możesz spróbować użyć prostej pętli, for(int i=0; ..; ..)
która nie ma tego problemu (ale nie zatrzyma cię, gdy inny wątek zmodyfikuje twoją listę). Można również użyć metod, takich jak list.removeAll(Collection)
, list.removeIf(Predicate)
. Również java.util.Collections
klasa ma kilka metod, które mogą być przydatne, np addAll(CollectionOfNewElements,list)
.
filter()
Funkcjonalny sposób wyglądałby tak:
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]
}
}
W tym rozwiązaniu oryginalna lista nie jest modyfikowana, ale powinna zawierać oczekiwany wynik w nowej liście, która jest dostępna pod tą samą zmienną co stara
Aby to zrobić modyfikację konstrukcyjną od źródła strumienia, jak Pshemo wspomniano w swojej odpowiedzi, jednym rozwiązaniem jest utworzenie nowej instancji Collection
jak ArrayList
z rzeczami wewnątrz pierwotnej liście; iteruj po nowej liście i wykonaj operacje na liście podstawowej.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
Aby pozbyć się ConcurrentModificationException, użyj CopyOnWriteArrayList
List<Something>
- nie masz pojęcia, czy jest to CopyOnWriteArrayList. Więc to bardziej jak mierny komentarz, ale nie jest to prawdziwa odpowiedź.
Zamiast tworzyć dziwne rzeczy, można po prostu filter()
a następnie map()
Twój wynik.
Jest to o wiele bardziej czytelne i pewne. Strumienie zrobią to tylko w jednej pętli.
Możesz skorzystać z, removeIf
aby warunkowo usunąć dane z listy.
Np .: - Jeśli chcesz usunąć wszystkie liczby parzyste z listy, możesz to zrobić w następujący sposób.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
Tak, możesz również modyfikować lub aktualizować wartości obiektów na liście w swoim przypadku:
users.stream().forEach(u -> u.setProperty("some_value"))
Jednak powyższa instrukcja spowoduje aktualizację obiektów źródłowych. Co może być nie do przyjęcia w większości przypadków.
Na szczęście mamy inny sposób:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
Co zwraca zaktualizowaną listę z powrotem, bez utrudniania starej.
Jak wspomniano wcześniej - nie możesz modyfikować oryginalnej listy, ale możesz przesyłać strumieniowo, modyfikować i zbierać elementy do nowej listy. Oto prosty przykład, jak zmodyfikować element string.
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]
}
}
To może być trochę za późno. Ale tutaj jest jeden z zastosowań. Ma to na celu uzyskanie liczby plików.
Utwórz wskaźnik do pamięci (w tym przypadku nowy obiekt) i zmodyfikuj właściwość obiektu. Strumień Java 8 nie pozwala na modyfikację samego wskaźnika, a zatem jeśli zadeklarujesz tylko jako zmienną i spróbujesz zwiększyć w strumieniu, nigdy nie zadziała i nie wyrzuci wyjątku kompilatora w pierwszej kolejności
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 + "]";
}
}