Sửa đổi các đối tượng trong luồng trong Java8 khi lặp lại


87

Trong các luồng Java8, tôi có được phép sửa đổi / cập nhật các đối tượng bên trong không? Ví dụ. List<User> users:

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

Câu trả lời:


101

Có, bạn có thể sửa đổi trạng thái của các đối tượng bên trong luồng của mình, nhưng thường thì bạn nên tránh sửa đổi trạng thái của nguồn luồng. Từ phần không can thiệp của tài liệu gói luồng, chúng ta có thể đọc rằng:

Đối với hầu hết các nguồn dữ liệu, việc ngăn chặn sự can thiệp có nghĩa là đảm bảo rằng nguồn dữ liệu không bị sửa đổi trong quá trình thực hiện đường truyền luồng. Ngoại lệ đáng chú ý đối với điều này là các luồng có nguồn là các tập hợp đồng thời, được thiết kế đặc biệt để xử lý sửa đổi đồng thời. Nguồn luồng đồng thời là những nguồn có đặc điểm Spliteratorbáo cáo CONCURRENT.

Vì vậy, điều này là OK

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

nhưng điều này trong hầu hết các trường hợp không phải là

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

và có thể ném ConcurrentModificationExceptionhoặc thậm chí các ngoại lệ bất ngờ khác như 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

4
Các giải pháp là gì nếu bạn muốn sửa đổi người dùng?
Augustas

2
@Augustas Tất cả phụ thuộc vào cách bạn muốn sửa đổi danh sách này. Nhưng nói chung, bạn nên tránh Iteratorđiều này ngăn cản việc sửa đổi danh sách (loại bỏ / thêm phần tử mới) trong khi lặp và cả luồng và cho từng vòng đều đang sử dụng nó. Bạn có thể thử sử dụng vòng lặp đơn giản như for(int i=0; ..; ..)không có vấn đề này (nhưng sẽ không dừng bạn khi chuỗi khác sẽ sửa đổi danh sách của bạn). Bạn cũng có thể sử dụng các phương pháp như list.removeAll(Collection), list.removeIf(Predicate). Ngoài ra java.util.Collectionslớp có một số phương thức có thể hữu ích như addAll(CollectionOfNewElements,list).
Pshemo

@Pshemo, một giải pháp cho điều đó là tạo một phiên bản mới của Bộ sưu tập như ArrayList với các mục bên trong danh sách chính của bạn; lặp qua danh sách mới và thực hiện thao tác trên danh sách chính: new ArrayList <> (users) .stream.forEach (u -> users.remove (u));
MNZ

2
@Blauhirn Giải quyết cái gì? Chúng tôi không được phép sửa đổi nguồn luồng trong khi sử dụng luồng, nhưng được phép sửa đổi trạng thái của các phần tử của luồng đó. Không quan trọng nếu chúng tôi sử dụng map, foreach hoặc các phương thức stream khác.
Pshemo

1
Thay vì sửa đổi bộ sưu tập Tôi sẽ đề nghị sử dụngfilter()
jontro

5

Cách chức năng sẽ là:

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

Trong giải pháp này, danh sách ban đầu không được sửa đổi, nhưng phải chứa kết quả mong đợi của bạn trong một danh sách mới có thể truy cập theo cùng một biến với danh sách cũ


3

Để thực hiện sửa đổi cấu trúc trên nguồn của luồng, như Pshemo đã đề cập trong câu trả lời của mình, một giải pháp là tạo một phiên bản Collectiontương tự mới ArrayListvới các mục bên trong danh sách chính của bạn; lặp qua danh sách mới và thực hiện các thao tác trên danh sách chính.

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

1

Để thoát khỏi ConcurrentModificationException, hãy sử dụng CopyOnWriteArrayList


2
Điều đó thực sự đúng, nhưng: điều này phụ thuộc vào lớp cụ thể được sử dụng ở đây. Nhưng khi bạn chỉ biết rằng bạn có một List<Something>- bạn không biết đó có phải là CopyOnWriteArrayList hay không. Vì vậy, điều này giống như một bình luận tầm thường, nhưng không phải là một câu trả lời thực sự.
GhostCat, 17/07/17

Nếu anh ấy đã đăng nó dưới dạng bình luận, ai đó rất có thể đã nói với anh ấy rằng anh ấy không nên đăng câu trả lời dưới dạng bình luận.
Bill K

1

Thay vì tạo ra những điều kỳ lạ, bạn có thể chỉ filter()và sau đó map()là kết quả của bạn.

Điều này dễ đọc hơn và chắc chắn. Luồng sẽ thực hiện nó chỉ trong một vòng lặp.


1

Có, bạn cũng có thể sửa đổi hoặc cập nhật giá trị của các đối tượng trong danh sách trong trường hợp của mình:

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

Tuy nhiên, câu lệnh trên sẽ cập nhật các đối tượng nguồn. Điều này có thể không được chấp nhận trong hầu hết các trường hợp.

May mắn thay, chúng tôi có một cách khác như:

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

Điều này sẽ trả về một danh sách đã cập nhật mà không cản trở danh sách cũ.


0

Như nó đã được đề cập trước đây - bạn không thể sửa đổi danh sách ban đầu, nhưng bạn có thể phát trực tuyến, sửa đổi và thu thập các mục vào danh sách mới. Đây là ví dụ đơn giản về cách sửa đổi phần tử chuỗi.

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

0

Bạn có thể sử dụng removeIfđể xóa dữ liệu khỏi danh sách một cách có điều kiện.

Vd: - Muốn xóa tất cả các số chẵn trong một danh sách, bạn có thể thực hiện như sau.

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

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

-1

Điều này có thể là một chút muộn. Nhưng đây là một trong những cách sử dụng. Điều này để đếm số lượng tệp.

Tạo một con trỏ tới bộ nhớ (một đối tượng mới trong trường hợp này) và sửa đổi thuộc tính của đối tượng. Luồng Java 8 không cho phép sửa đổi chính con trỏ và do đó nếu bạn khai báo chỉ được tính là một biến và cố gắng tăng lên trong luồng, nó sẽ không bao giờ hoạt động và ném một ngoại lệ trình biên dịch ngay từ đầu

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



    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.