Đây có phải là một antipotype để sử dụng peek () để sửa đổi một phần tử luồng không?


36

Giả sử tôi có một luồng điều và tôi muốn "làm phong phú" chúng giữa dòng, tôi có thể sử dụng peek()để làm điều này, ví dụ:

streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Giả sử rằng việc thay đổi Điều ở thời điểm này trong mã là hành vi đúng - ví dụ: thingMutatorphương thức có thể đặt trường "LastProcessed" thành thời điểm hiện tại.

Tuy nhiên, peek()trong hầu hết các bối cảnh có nghĩa là "nhìn, nhưng đừng chạm vào".

Là sử dụng peek()để đột biến các yếu tố dòng một antipotype hoặc không khuyến khích?

Chỉnh sửa:

Cách tiếp cận khác, thông thường hơn sẽ là chuyển đổi người tiêu dùng:

private void thingMutator(Thing thing) {
    thing.setLastProcessed(System.currentTimeMillis());
}

đến một hàm trả về tham số:

private Thing thingMutator(Thing thing) {
    thing.setLastProcessed(currentTimeMillis());
    return thing;
}

và sử dụng map()thay thế:

stream.map(this::thingMutator)...

Nhưng điều đó giới thiệu mã chiếu lệ ( returnvà) và tôi không tin nó rõ ràng hơn, bởi vì bạn biết peek()trả về cùng một đối tượng, nhưng với map()một cái nhìn thậm chí không rõ ràng rằng đó là cùng một loại đối tượng.

Hơn nữa, với peek()bạn có thể có một lambda đột biến, nhưng với map()bạn phải xây dựng một con tàu đắm. Đối chiếu:

stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)

Tôi nghĩ rằng peek()phiên bản rõ ràng hơn và lambda rõ ràng đang biến đổi, vì vậy không có tác dụng phụ "bí ẩn". Tương tự, nếu một tham chiếu phương thức được sử dụng và tên của phương thức ngụ ý rõ ràng đột biến, thì điều đó cũng rõ ràng và rõ ràng.

Về lưu ý cá nhân, tôi không ngại sử dụng peek()để biến đổi - tôi thấy nó rất tiện lợi.



1
Bạn đã sử dụng peekvới một luồng tạo các phần tử của nó một cách linh hoạt chưa? Nó vẫn hoạt động, hoặc những thay đổi bị mất? Sửa đổi các yếu tố của một luồng âm thanh không đáng tin cậy đối với tôi.
Sebastian Redl

@SebastianRedl Tôi đã tìm thấy nó đáng tin cậy 100%. Lần đầu tiên tôi sử dụng nó để thu thập trong quá trình xử lý: List<Thing> list; things.stream().peek(list::add).forEach(...);rất tiện dụng. Gần đây. Tôi đã sử dụng nó để thêm thông tin để xuất bản : Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());. Tôi biết có nhiều cách khác để làm ví dụ này, nhưng tôi đơn giản hóa quá mức ở đây. Sử dụng peek()sản xuất nhỏ gọn hơn, và mã thanh lịch hơn IMHO. Dễ đọc sang một bên, câu hỏi này thực sự là về những gì bạn đã nêu ra; nó có an toàn / đáng tin cậy không?
Bohemian


@Bohantic. Bạn vẫn đang thực hành phương pháp này với peek? Tôi có câu hỏi tương tự trên stackoverflow và hy vọng bạn có thể xem nó và đưa ra phản hồi của bạn. Cảm ơn. stackoverflow.com/questions/47356992/
hy

Câu trả lời:


23

Bạn đúng, "peek" theo nghĩa tiếng Anh của từ này có nghĩa là "nhìn, nhưng đừng chạm vào."

Tuy nhiên , JavaDoc tuyên bố:

nhìn trộm

Stream peek (Hành động của người tiêu dùng)

Trả về một luồng bao gồm các phần tử của luồng này, ngoài ra thực hiện hành động được cung cấp trên mỗi phần tử khi các phần tử được tiêu thụ từ luồng kết quả.

Từ khóa: "thực hiện ... hành động" và "tiêu thụ". JavaDoc rất rõ ràng rằng chúng ta sẽ peekcó khả năng sửa đổi luồng.

Tuy nhiên , JavaDoc cũng tuyên bố:

Lưu ý API:

Phương thức này tồn tại chủ yếu để hỗ trợ gỡ lỗi, nơi bạn muốn xem các phần tử khi chúng chảy qua một điểm nhất định trong đường ống

Điều này chỉ ra rằng nó được dự định nhiều hơn để quan sát, ví dụ: các phần tử ghi nhật ký trong luồng.

Những gì tôi nhận được từ tất cả những điều này là chúng ta có thể thực hiện các hành động bằng cách sử dụng các phần tử trong luồng, nhưng nên tránh làm biến đổi các phần tử trong luồng. Ví dụ, hãy tiếp tục và gọi các phương thức trên các đối tượng, nhưng cố gắng tránh các hoạt động đột biến trên chúng.

Ít nhất, tôi sẽ thêm một nhận xét ngắn gọn vào mã của bạn dọc theo những dòng này:

// Note: this peek() call will modify the Things in the stream.
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

Ý kiến ​​khác nhau về tính hữu ích của những bình luận như vậy, nhưng tôi sẽ sử dụng một nhận xét như vậy trong trường hợp này.


1
Tại sao bạn cần một nhận xét nếu tên phương thức báo hiệu rõ ràng về đột biến, ví dụ thingMutatorhoặc cụ thể hơn resetLastProcessed, trừ khi có lý do thuyết phục, các nhận xét cần như đề xuất của bạn thường chỉ ra các lựa chọn kém về tên biến và / hoặc tên phương thức. Nếu tên hay được chọn, điều đó có đủ không? Hay bạn đang nói rằng mặc dù có một cái tên hay, nhưng bất cứ điều gì peek()giống như một điểm mù mà hầu hết các lập trình viên sẽ "lướt qua" (bỏ qua trực quan)? Ngoài ra, "chủ yếu để gỡ lỗi" không giống như "chỉ để gỡ lỗi" - những công dụng nào khác ngoài mục đích "chủ yếu" được dự định?
Bohemian

@Bohantic Tôi sẽ giải thích nó chủ yếu vì tên phương thức không trực quan. Và tôi không làm việc cho Oracle. Tôi không thuộc nhóm Java của họ. Tôi không biết họ dự định gì.

@Bohantic vì khi tìm kiếm những gì sửa đổi các yếu tố theo cách tôi không thích, tôi sẽ ngừng đọc dòng này tại "peek" vì "peek" không thay đổi bất cứ điều gì. Chỉ sau này khi tất cả các vị trí mã khác không chứa lỗi mà tôi đang theo đuổi thì tôi mới quay lại vấn đề này, có thể được trang bị một trình gỡ lỗi và sau đó có thể đi "omg, ai đã đặt mã sai lệch này?!"
Frank Hopkins

@Bohantic trong exampe ở đây, nơi mọi thứ đều nằm trong một dòng, dù sao thì người ta cũng cần đọc, nhưng người ta có thể bỏ qua nội dung của "peek" và thường một câu lệnh stream đi qua nhiều dòng với một thao tác truyền phát trên mỗi dòng
Frank Hopkins

1

Nó có thể dễ dàng bị hiểu sai, vì vậy tôi sẽ tránh sử dụng nó như thế này. Có lẽ tùy chọn tốt nhất là sử dụng hàm lambda để hợp nhất hai hành động bạn cần vào lệnh gọi forEach. Bạn cũng có thể muốn xem xét trả lại một đối tượng mới thay vì thay đổi đối tượng hiện tại - nó có thể kém hiệu quả hơn một chút, nhưng nó có thể dẫn đến mã dễ đọc hơn và giảm khả năng vô tình sử dụng danh sách đã sửa đổi cho một hoạt động khác nên đã nhận được bản gốc.


-2

Lưu ý API cho chúng ta biết rằng phương thức này đã được thêm vào chủ yếu cho các hành động như gỡ lỗi / ghi nhật ký / bắn chỉ số, v.v.

@apiNote Phương thức này tồn tại chủ yếu để hỗ trợ gỡ lỗi, nơi bạn muốn * xem các phần tử khi chúng chảy qua một điểm nhất định trong đường ống: *

       {@code
     * Stream.of ("một", "hai", "ba", "bốn")
     * .filter (e -> e.length ()> 3)
     * .peek (e -> System.out.println ("Giá trị được lọc:" + e))
     * .map (Chuỗi :: toUpperCase)
     * .peek (e -> System.out.println ("Giá trị đã ánh xạ:" + e))
     * .collect (Collector.toList ());
     *}


2
Câu trả lời của bạn đã được Snowman's bảo vệ.
Vincent Savard

3
Và "chủ yếu" không có nghĩa là "chỉ".
Bohemian

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.