Yineleme sırasında Java8'de akış içindeki Nesneleri değiştirme


88

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:


102

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ı, Spliteratorraporları CONCURRENTkarakteristik 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 ConcurrentModificationExceptionNPE 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

4
Kullanıcıları değiştirmek istiyorsanız çözümler nelerdir?
Augustas

2
@Augustas Her şey bu listeyi nasıl değiştirmek istediğinize bağlıdır. Ancak 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.Collectionssınıfın, gibi yararlı olabilecek birkaç yöntemi vardır addAll(CollectionOfNewElements,list).
Pshemo

@Pshemo, buna bir çözüm, birincil listenizdeki öğelerle ArrayList gibi bir Koleksiyonun yeni bir örneğini oluşturmaktır; yeni listeyi yineleyin ve birincil listede işlemi yapın: new ArrayList <> (users) .stream.forEach (u -> users.remove (u));
MNZ

2
@Blauhirn Neyi çözecek? Akışı kullanırken akışın kaynağını değiştirmemize izin verilmez, ancak öğelerinin durumunu değiştirmemize izin verilir. Map, foreach veya diğer akış yöntemlerini kullanmamızın bir önemi yok.
Pshemo

1
Koleksiyonu değiştirmek yerine kullanmanızı öneririmfilter()
jontro

5

İş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.


3

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 Collectionbenzer ö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));

1

ConcurrentModificationException'dan kurtulmak için CopyOnWriteArrayList'i kullanın


2
Bu aslında doğrudur, ancak: bu burada kullanılan belirli sınıfa bağlıdır. Ancak yalnızca bir sahip olduğunuzu bildiğinizde 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.
GhostCat

Bunu bir yorum olarak yayınlamış olsaydı, büyük olasılıkla birisi ona cevapları yorum olarak göndermemesi gerektiğini söylerdi.
Bill K

1

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.


1

removeIfVerileri 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);

1

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.


0

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]
    }
}

-1

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 + "]";
        }



    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.