Hoạt động dòng trung gian không được đánh giá về số lượng


33

Có vẻ như tôi đang gặp khó khăn trong việc hiểu cách Java kết hợp các hoạt động luồng thành một đường truyền luồng.

Khi thực thi đoạn mã sau

public
 static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

Bảng điều khiển chỉ in 4. Đối StringBuildertượng vẫn có giá trị "".

Khi tôi thêm thao tác lọc: filter(s -> true)

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .filter(s -> true)
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

Đầu ra thay đổi thành:

4
1234

Làm thế nào để hoạt động bộ lọc dường như dư thừa này thay đổi hành vi của đường ống tổng hợp?


2
Hấp dẫn !!!
uneq95

3
Tôi sẽ tưởng tượng đây là hành vi cụ thể thực hiện; có lẽ bởi vì luồng đầu tiên có kích thước đã biết, nhưng luồng thứ hai thì không và luồng có kích thước xác định liệu các hoạt động trung gian có được thực hiện hay không.
Andy Turner

Không quan tâm, điều gì xảy ra nếu bạn đảo ngược bộ lọc và bản đồ?
Andy Turner

Đã lập trình một chút trong Haskell, nó có mùi hơi giống như một số đánh giá lười biếng đang diễn ra ở đây. Một tìm kiếm google trở lại, luồng đó thực sự có một số sự lười biếng. Có thể đó là trường hợp? Và không có bộ lọc, nếu java đủ thông minh, không cần thực sự thực hiện ánh xạ.
Frederik

@AndyTurner Nó cho kết quả tương tự, ngay cả khi đảo ngược
uneq95

Câu trả lời:


39

Các count()hoạt động thiết bị đầu cuối, trong phiên bản của tôi về JDK, kết thúc thực hiện đoạn mã sau:

if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
    return spliterator.getExactSizeIfKnown();
return super.evaluateSequential(helper, spliterator);

Nếu có một filter()hoạt động trong đường ống hoạt động, kích thước của luồng, được biết đến ban đầu, không thể biết được nữa (vì filtercó thể từ chối một số thành phần của luồng). Nênif khối không được thực thi, các hoạt động trung gian được thực thi và StringBuilder được sửa đổi.

Mặt khác, nếu bạn chỉ có map() trong đường ống, số lượng phần tử trong luồng được đảm bảo giống với số lượng phần tử ban đầu. Vì vậy, khối if được thực thi và kích thước được trả về trực tiếp mà không đánh giá các hoạt động trung gian.

Lưu ý rằng lambda được thông qua để map()vi phạm hợp đồng được xác định trong tài liệu: nó được coi là một hoạt động không can thiệp, không trạng thái, nhưng nó không phải là không quốc tịch. Vì vậy, có một kết quả khác nhau trong cả hai trường hợp không thể được coi là một lỗi.


Bởi vì flatMap()có thể thay đổi số lượng các yếu tố, đó có phải là lý do tại sao ban đầu nó háo hức (bây giờ lười biếng)? Vì vậy, giải pháp thay thế sẽ là sử dụng forEach()và tính riêng nếu map()ở dạng hiện tại vi phạm hợp đồng, tôi đoán vậy.
Frederik

3
Về FlatMap, tôi không nghĩ vậy. Đó là, AFAIK, vì nó ban đầu đơn giản hơn để làm cho nó háo hức. Có, sử dụng một luồng, với map (), để tạo hiệu ứng phụ là một ý tưởng tồi.
JB Nizet

Bạn có gợi ý về cách đạt được đầu ra đầy đủ 4 1234mà không cần sử dụng bộ lọc bổ sung hoặc tạo hiệu ứng phụ trong hoạt động map () không?
atalantus

1
int count = array.length; String result = String.join("", array);
JB Nizet

1
hoặc bạn có thể sử dụng forEach nếu bạn thực sự muốn sử dụng StringBuilder hoặc bạn có thể sử dụngCollectors.joining("")
njzk2

19

Trong jdk-9, nó đã được ghi lại rõ ràng trong các tài liệu java

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 thao tác đếm .)

Lưu ý API:

Việc triển khai có thể chọn không thực hiện đường ống truyền phát (theo tuần tự hoặc song song) nếu nó có khả năng tính toán số lượng trực tiếp từ nguồn phát. Trong các trường hợp như vậy, không có phần tử nguồn nào sẽ được duyệt và không có hoạt động trung gian nào được đánh giá. Các thông số hành vi với các tác dụng phụ, được khuyến khích mạnh mẽ ngoại trừ các trường hợp vô hại như gỡ lỗi, có thể bị ảnh hưởng. Ví dụ: hãy xem xét các luồng sau:

 List<String> l = Arrays.asList("A", "B", "C", "D");
 long count = l.stream().peek(System.out::println).count();

Số lượng phần tử được bao phủ bởi nguồn phát, Danh sách, đã biết và hoạt động trung gian, nhìn trộm, không đưa vào hoặc xóa phần tử khỏi luồng (như trường hợp đối với hoạt động của FlatMap hoặc bộ lọc). Do đó, số lượng là kích thước của Danh sách và không cần thực hiện đường ống và, như một hiệu ứng phụ, in ra các thành phần danh sách.


0

Đây không phải là những gì .map dành cho. Nó được cho là được sử dụng để biến một luồng "Cái gì đó" thành một dòng "Cái gì đó khác". Trong trường hợp này, bạn đang sử dụng bản đồ để nối một chuỗi vào Stringbuilder bên ngoài, sau đó bạn có một luồng "Stringbuilder", mỗi chuỗi được tạo bởi thao tác bản đồ nối thêm một số vào Stringbuilder gốc.

Luồng của bạn không thực sự làm bất cứ điều gì với kết quả được ánh xạ trong luồng, do đó, hoàn toàn hợp lý khi cho rằng bước này có thể được bỏ qua bởi bộ xử lý luồng. Bạn đang tính đến các tác dụng phụ để thực hiện công việc, phá vỡ mô hình chức năng của bản đồ. Bạn sẽ được phục vụ tốt hơn bằng cách sử dụng forEach để làm điều này. Thực hiện đếm hoàn toàn dưới dạng một luồng riêng biệt hoặc đặt bộ đếm bằng AtomicInt trong forEach.

Bộ lọc buộc nó phải chạy nội dung luồng vì giờ đây nó phải làm một cái gì đó có ý nghĩa về mặt ý nghĩa với từng thành phần luồng.

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.