Memodifikasi Objek dalam aliran di Java8 saat melakukan iterasi

88

Di aliran Java8, apakah saya diizinkan untuk mengubah / memperbarui objek di dalamnya? Misalnya. List<User> users:

users.stream().forEach(u -> u.setProperty("value"))
Tejas Gokhale
sumber

Jawaban:

102

Ya, Anda dapat mengubah status objek di dalam aliran Anda, tetapi paling sering Anda harus menghindari modifikasi status sumber aliran. Dari bagian non-interferensi dari dokumentasi paket streaming kita dapat membaca bahwa:

Untuk sebagian besar sumber data, mencegah interferensi berarti memastikan bahwa sumber data tidak diubah sama sekali selama eksekusi pipeline streaming. Pengecualian penting untuk ini adalah aliran yang sumbernya adalah koleksi serentak, yang dirancang khusus untuk menangani modifikasi serentak. Sumber aliran bersamaan adalah mereka yang Spliteratormelaporkan CONCURRENTkarakteristiknya.

Jadi ini OK

  List<User> users = getUsers();
  users.stream().forEach(u -> u.setProperty(value));
//                       ^    ^^^^^^^^^^^^^

tetapi ini dalam banyak kasus tidak

  users.stream().forEach(u -> users.remove(u));
//^^^^^                       ^^^^^^^^^^^^

dan mungkin melempar ConcurrentModificationExceptionatau bahkan pengecualian tak terduga lainnya seperti 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
Pshemo
sumber
4
Apa solusi jika Anda ingin mengubah pengguna?
Augustas
2
@Augustas Semuanya tergantung pada bagaimana Anda ingin mengubah daftar ini. Tetapi secara umum Anda harus menghindari Iteratoryang mencegah mengubah daftar (menghapus / menambahkan elemen baru) saat melakukan iterasi dan kedua aliran dan untuk-setiap loop menggunakannya. Anda dapat mencoba menggunakan loop sederhana seperti for(int i=0; ..; ..)yang tidak memiliki masalah ini (tetapi tidak akan menghentikan Anda ketika utas lain akan mengubah daftar Anda). Anda juga bisa menggunakan metode seperti list.removeAll(Collection), list.removeIf(Predicate). Juga java.util.Collectionskelas memiliki beberapa metode yang dapat berguna seperti addAll(CollectionOfNewElements,list).
Pshemo
@Pshemo, salah satu solusi untuk itu adalah membuat instance baru dari Collection seperti ArrayList dengan item di dalam daftar utama Anda; iterasi ke atas daftar baru, dan lakukan operasi pada daftar utama: new ArrayList <> (users) .stream.forEach (u -> users.remove (u));
MNZ
2
@Blauhirn Pecahkan apa? Kami tidak diizinkan mengubah sumber aliran saat menggunakan aliran, tetapi diizinkan untuk mengubah status elemennya. Tidak masalah jika kita menggunakan map, foreach atau metode aliran lainnya.
Pshemo
1
Alih-alih memodifikasi koleksi, saya sarankan menggunakanfilter()
jontro
5

Cara fungsionalnya adalah:

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

Dalam solusi ini daftar asli tidak diubah, tetapi harus berisi hasil yang Anda harapkan dalam daftar baru yang dapat diakses di bawah variabel yang sama dengan yang lama

Andreas M. Oberheim
sumber
3

Untuk melakukan modifikasi struktural pada sumber aliran, seperti yang disebutkan Pshemo dalam jawabannya, salah satu solusinya adalah membuat instance baru dari Collectionsejenis ArrayListdengan item di dalam daftar utama Anda; mengulangi daftar baru, dan melakukan operasi pada daftar utama.

new ArrayList<>(users).stream().forEach(u -> users.remove(u));
MNZ
sumber
1

Untuk menyingkirkan ConcurrentModificationException Gunakan CopyOnWriteArrayList

Krupali_wadekar
sumber
2
Itu sebenarnya benar, tetapi: ini tergantung pada kelas spesifik yang digunakan di sini. Tetapi ketika Anda hanya tahu bahwa Anda memiliki List<Something>- Anda tidak tahu apakah itu CopyOnWriteArrayList. Jadi ini lebih seperti komentar yang biasa-biasa saja, tapi bukan jawaban yang sebenarnya.
GhostCat
Jika dia mempostingnya sebagai komentar, seseorang kemungkinan besar akan mengatakan kepadanya bahwa dia tidak boleh memposting jawaban sebagai komentar.
Bill K
1

Alih-alih membuat hal-hal aneh, Anda bisa filter()dan kemudian map()hasilnya.

Ini jauh lebih mudah dibaca dan pasti. Aliran akan membuatnya hanya dalam satu putaran.

Nicolas Zozol
sumber
1

Anda dapat menggunakan removeIfuntuk menghapus data dari daftar secara bersyarat.

Misalnya: - Jika Anda ingin menghapus semua nomor genap dari daftar, Anda dapat melakukannya sebagai berikut.

    final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());

    list.removeIf(number -> number % 2 == 0);
JJ
sumber
1

Ya, Anda juga dapat mengubah atau memperbarui nilai objek dalam daftar dalam kasus Anda:

users.stream().forEach(u -> u.setProperty("some_value"))

Namun, pernyataan di atas akan membuat pembaruan pada objek sumber. Yang mungkin tidak dapat diterima dalam banyak kasus.

Untungnya, kami memiliki cara lain seperti:

List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());

Yang mengembalikan daftar yang diperbarui kembali, tanpa menghambat yang lama.

Gaurav Khanzode
sumber
0

Seperti yang disebutkan sebelumnya - Anda tidak dapat mengubah daftar asli, tetapi Anda dapat mengalirkan, mengubah, dan mengumpulkan item ke dalam daftar baru. Berikut adalah contoh sederhana bagaimana memodifikasi elemen 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]
    }
}
Vadim
sumber
-1

Ini mungkin sedikit terlambat. Tapi ini salah satu penggunaannya. Ini untuk mendapatkan hitungan jumlah file.

Buat pointer ke memori (object baru dalam kasus ini) dan modifikasi properti objek. Aliran Java 8 tidak mengizinkan untuk memodifikasi penunjuk itu sendiri dan karenanya jika Anda mendeklarasikan hanya menghitung sebagai variabel dan mencoba menambah dalam aliran itu tidak akan pernah berfungsi dan memunculkan pengecualian kompiler di tempat pertama

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



    }
Joey587
sumber