Java8 akışlarında, içindeki nesneleri değiştirmeye / güncellemeye izin verilir mi? Örneğin. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Java8 akışlarında, içindeki nesneleri değiştirmeye / güncellemeye izin verilir mi? Örneğin. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Yanıtlar:
Evet, akışınız içindeki nesnelerin durumunu değiştirebilirsiniz, ancak çoğu zaman akışın kaynağı durumunu değiştirmekten kaçınmalısınız . Gönderen karışmama bunu okuyabilir akışı paket belgelerin bölümünde:
Çoğu veri kaynağı için, girişimi önlemek, veri kaynağının akış ardışık düzeninin yürütülmesi sırasında hiç değiştirilmemesini sağlamak anlamına gelir . Bunun dikkate değer istisnası, kaynakları eşzamanlı koleksiyonlar olan ve eşzamanlı değişiklikleri işlemek için özel olarak tasarlanmış akışlardır. Eşzamanlı akış kaynakları,
Spliterator
raporlarıCONCURRENT
karakteristik olanlardır .
Yani bu tamam
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
ama bu çoğu durumda
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
ve ConcurrentModificationException
NPE gibi diğer beklenmedik istisnaları ortaya çıkarabilir :
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
, yineleme sırasında listenin değiştirilmesini engelleyen (yeni öğeleri kaldıran / ekleyen) ve hem akışlar hem de her döngü için onu kullananlardan kaçınmalısınız . for(int i=0; ..; ..)
Bu sorunu olmayan basit döngüyü kullanmayı deneyebilirsiniz (ancak diğer iş parçacığı listenizi değiştirdiğinde sizi durdurmayacaktır). Ayrıca list.removeAll(Collection)
, gibi yöntemler de kullanabilirsiniz list.removeIf(Predicate)
. Ayrıca java.util.Collections
sınıfın, gibi yararlı olabilecek birkaç yöntemi vardır addAll(CollectionOfNewElements,list)
.
filter()
İşlevsel yol şu şekilde olacaktır:
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]
}
}
Bu çözümde orijinal liste değiştirilmemiştir, ancak beklenen sonucunuzu eskisiyle aynı değişken altında erişilebilen yeni bir listede içermelidir.
Pshemo'nun cevabında bahsettiği gibi, akış kaynağında yapısal değişiklik yapmak için bir çözüm, birincil listenizdeki öğelerle yeni bir Collection
benzer örneği oluşturmaktır ArrayList
; yeni listeyi yineleyin ve birincil listedeki işlemleri yapın.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
ConcurrentModificationException'dan kurtulmak için CopyOnWriteArrayList'i kullanın
List<Something>
- bunun bir CopyOnWriteArrayList olup olmadığı hakkında hiçbir fikriniz olmaz. Yani bu daha çok vasat bir yorum gibi, ama gerçek bir cevap değil.
Garip şeyler yaratmak yerine, sadece filter()
ve sonra map()
sonucunuzu yapabilirsiniz.
Bu çok daha okunaklı ve kesin. Akışlar bunu yalnızca bir döngüde yapacak.
removeIf
Verileri bir listeden koşullu olarak kaldırmak için kullanabilirsiniz .
Örneğin: - Bir listeden tüm çift sayıları kaldırmak istiyorsanız, bunu aşağıdaki gibi yapabilirsiniz.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
Evet, sizin durumunuzda benzer şekilde listedeki nesnelerin değerlerini değiştirebilir veya güncelleyebilirsiniz:
users.stream().forEach(u -> u.setProperty("some_value"))
Ancak, yukarıdaki ifade kaynak nesneler üzerinde güncellemeler yapacaktır. Çoğu durumda kabul edilebilir olmayabilir.
Neyse ki, başka bir yolumuz var:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
Bu, eskisini engellemeden güncellenmiş bir listeyi geri döndürür.
Daha önce bahsedildiği gibi - orijinal listeyi değiştiremezsiniz, ancak öğeleri yeni listeye aktarabilir, değiştirebilir ve toplayabilirsiniz. İşte string elemanının nasıl değiştirileceği basit bir örnek.
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]
}
}
Bu biraz geç olabilir. Ama işte kullanımlardan biri. Bu, dosya sayısının sayısını almak için.
Belleğe bir işaretçi (bu durumda yeni bir nesne) oluşturun ve nesnenin özelliğinin değiştirilmesini sağlayın. Java 8 akışı, işaretçinin kendisini değiştirmeye izin vermez ve bu nedenle, yalnızca bir değişken olarak saymayı bildirirseniz ve akış içinde artış yapmaya çalışırsanız, asla çalışmaz ve ilk etapta bir derleyici istisnası atmaz.
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 + "]";
}
}