Theo tài liệu trên trang web của Oracle [...]
Liên kết đó dành cho Java 8. Bạn có thể muốn đọc tài liệu cho Java 9 (xuất hiện vào năm 2017) và các phiên bản mới hơn, vì chúng rõ ràng hơn về vấn đề này. Đặc biệt:
Việc triển khai luồng được cho phép có vĩ độ đáng kể trong việc tối ưu hóa tính toán của kết quả. Ví dụ, việc triển khai luồng là miễn phí đối với các hoạt động (hoặc toàn bộ các giai đoạn) từ một đường truyền luồng - và do đó, yêu cầu gọi các tham số hành vi - nếu nó có thể chứng minh rằng nó sẽ không ảnh hưởng đến kết quả tính toán. Điều này có nghĩa là các tác dụng phụ của các tham số hành vi có thể không phải luôn luôn được thực thi và không nên dựa vào, trừ khi có quy định khác (chẳng hạn như bởi các hoạt động của thiết bị đầu cuối forEach
và forEachOrdered
). (Để biết ví dụ cụ thể về tối ưu hóa như vậy, hãy xem ghi chú API được ghi lại trong count()
hoạt động. Để biết thêm chi tiết, hãy xem phần tác dụng phụ của tài liệu gói luồng.)
Nguồn: Javadoc của Java 9 cho Stream
giao diện .
Và cũng là phiên bản cập nhật của tài liệu bạn trích dẫn:
Phản ứng phụ
Nhìn chung, các tác dụng phụ trong các tham số hành vi đối với các hoạt động truyền phát, không được khuyến khích, vì chúng thường có thể dẫn đến việc vô tình vi phạm yêu cầu không trạng thái, cũng như các nguy cơ về an toàn luồng khác.
Nếu các tham số hành vi có tác dụng phụ, trừ khi được nêu rõ ràng, không có đảm bảo nào về :
- khả năng hiển thị của các tác dụng phụ cho các chủ đề khác;
- rằng các hoạt động khác nhau trên phần tử "giống nhau" trong cùng một đường truyền luồng được thực thi trong cùng một luồng; và
- rằng các tham số hành vi luôn luôn được gọi, vì việc triển khai luồng là miễn phí để tách các hoạt động (hoặc toàn bộ các giai đoạn) khỏi đường ống dẫn nếu nó có thể chứng minh rằng nó sẽ không ảnh hưởng đến kết quả tính toán.
Thứ tự của các tác dụng phụ có thể gây ngạc nhiên. Ngay cả khi một đường ống bị hạn chế tạo ra kết quả phù hợp với thứ tự bắt gặp của nguồn phát (ví dụ: IntStream.range(0,5).parallel().map(x -> x*2).toArray()
phải tạo ra [0, 2, 4, 6, 8]
), không có đảm bảo nào được thực hiện theo thứ tự mà hàm ánh xạ được áp dụng cho các phần tử riêng lẻ hoặc trong luồng nào bất kỳ tham số hành vi nào được thực thi cho một phần tử đã cho.
Các tác dụng phụ của tác dụng phụ cũng có thể gây ngạc nhiên. Ngoại trừ các hoạt động đầu cuối forEach
vàforEachOrdered
, tác dụng phụ của các tham số hành vi có thể không luôn luôn được thực thi khi thực hiện luồng có thể tối ưu hóa việc thực hiện các tham số hành vi mà không ảnh hưởng đến kết quả tính toán. (Để biết ví dụ cụ thể, hãy xem ghi chú API được ghi lại trong count
hoạt động.)
Nguồn: Javadoc của Java 9 cho java.util.stream
gói .
Tất cả nhấn mạnh của tôi.
Như bạn có thể thấy, tài liệu chính thức hiện tại sẽ đi sâu vào chi tiết hơn về các vấn đề mà bạn có thể gặp phải nếu bạn quyết định sử dụng các tác dụng phụ trong hoạt động truyền phát của mình. Nó cũng rất rõ ràng forEach
và forEachOrdered
là hoạt động đầu cuối duy nhất mà việc thực hiện các tác dụng phụ được đảm bảo (lưu ý bạn, vấn đề an toàn luồng vẫn được áp dụng, như các ví dụ chính thức cho thấy).
Điều đó đang được nói, và liên quan đến mã cụ thể của bạn và chỉ nói mã:
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
Tôi thấy không có vấn đề nào liên quan đến Streams với mã đã nói.
- Các
.map()
bước sẽ được thực hiện bởi vì .collect()
(một giảm có thể thay đổi hoạt động, đó là những gì các doc chính thức đề nghị thay vì những thứ như .forEach(list::add)
) dựa trên .map()
'đầu ra s, và vì điều này (ví dụ saveCar()
' s) đầu ra là khác nhau hơn so với đầu vào của nó, con suối không thể "chứng minh rằng [eliding] nó sẽ không ảnh hưởng đến kết quả tính toán " .
- Nó không phải là
parallelStream()
vì vậy nó không nên đưa ra bất kỳ vấn đề tương tranh nào không tồn tại trước đó (tất nhiên, nếu ai đó thêm vào .parallel()
sau đó thì các vấn đề có thể phát sinh giống như nếu ai đó quyết định song song hóa một for
vòng lặp bằng cách kích hoạt các luồng mới cho các tính toán bên trong ).
Điều đó không có nghĩa là mã trong ví dụ đó là Good Code ™. Trình tự .stream.map(::someSideEffect()).collect()
như một cách thực hiện các hoạt động hiệu ứng phụ cho mọi mục trong bộ sưu tập có thể trông đơn giản / ngắn / thanh lịch hơn? hơn for
đối tác của nó , và đôi khi nó có thể được. Tuy nhiên, như Eugene, Holger và một số người khác nói với bạn, có nhiều cách tốt hơn để tiếp cận điều này.
Như một suy nghĩ nhanh: chi phí để khởi động Stream
so với lặp lại đơn giản for
là không đáng kể trừ khi bạn có nhiều vật phẩm và nếu bạn có nhiều vật phẩm thì bạn: a) có thể không muốn truy cập DB mới đối với mỗi người, do đó, một saveAll(List items)
API sẽ tốt hơn; và b) có lẽ không muốn đạt hiệu suất xử lý nhiều các mục theo tuần tự, vì vậy bạn sẽ kết thúc bằng cách sử dụng song song và sau đó một loạt các vấn đề mới phát sinh.