Tại sao Iterable <T> không cung cấp các phương thức stream () và allelStream ()?


241

Tôi tự hỏi tại sao Iterablegiao diện không cung cấp stream()parallelStream()phương thức. Hãy xem xét các lớp sau:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

Đây là một cách thực hiện của Hand khi bạn có thể có thẻ trong tay khi chơi Trò chơi đánh bài.

Về cơ bản, nó bao bọc a List<Card>, đảm bảo công suất tối đa và cung cấp một số tính năng hữu ích khác. Nó là tốt hơn khi thực hiện nó trực tiếp như một List<Card>.

Bây giờ, đối với sự thuận tiện, tôi nghĩ rằng sẽ rất tốt khi thực hiện Iterable<Card>, như vậy bạn có thể sử dụng các vòng lặp nâng cao nếu bạn muốn lặp lại nó. ( HandLớp của tôi cũng cung cấp một get(int index)phương thức, do đó theo Iterable<Card>quan điểm của tôi là hợp lý.)

Các Iterablegiao diện cung cấp như sau (bỏ qua javadoc):

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Bây giờ bạn có thể có được một luồng với:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Vì vậy, vào câu hỏi thực sự:

  • Tại sao Iterable<T>không cung cấp một phương thức mặc định thực hiện stream()parallelStream(), tôi thấy không có gì có thể làm điều này không thể hoặc không mong muốn?

Một câu hỏi liên quan tôi tìm thấy là như sau: Tại sao Stream <T> không triển khai lặp lại <T>?
Điều này đủ kỳ lạ để đề nghị nó làm điều đó theo cách khác.


1
Tôi đoán đây là một câu hỏi hay cho Danh sách gửi thư Lambda .
Edwin Dalorzo

Tại sao nó lại kỳ quặc khi muốn lặp lại qua một luồng? Làm thế nào khác bạn có thể có thể break;lặp đi lặp lại? (Ok, Stream.findFirst()có thể là một giải pháp, nhưng điều đó có thể không đáp ứng tất cả các nhu cầu ...)
glglgl

Xem thêm Chuyển đổi Iterable thành Stream bằng Java 8 JDK để biết cách giải quyết thực tế.
Vadzim

Câu trả lời:


298

Đây không phải là một thiếu sót; đã có cuộc thảo luận chi tiết về danh sách EG vào tháng 6 năm 2013.

Các cuộc thảo luận dứt khoát của Nhóm chuyên gia bắt nguồn từ chủ đề này .

Mặc dù có vẻ "rõ ràng" (ngay cả với Nhóm chuyên gia, ban đầu) stream()dường như có ý nghĩa Iterable, nhưng thực tế Iterablequá chung chung đã trở thành một vấn đề, bởi vì chữ ký rõ ràng:

Stream<T> stream()

không phải luôn luôn là những gì bạn sẽ muốn. Ví dụ, Iterable<Integer>một số thứ mà phương thức truyền phát của họ trả về một IntStreamví dụ. Nhưng việc đưa stream()phương thức này lên cao trong hệ thống phân cấp sẽ khiến điều đó trở thành không thể. Vì vậy, thay vào đó, chúng tôi đã làm cho nó thực sự dễ dàng để thực hiện Streamtừ một Iterable, bằng cách cung cấp một spliterator()phương thức. Việc thực hiện stream()trong Collectionchỉ là:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Bất kỳ khách hàng nào cũng có thể nhận được luồng họ muốn từ một Iterable:

Stream s = StreamSupport.stream(iter.spliterator(), false);

Cuối cùng, chúng tôi đã kết luận rằng thêm stream()vào Iterablesẽ là một sai lầm.


8
Tôi thấy, trước hết, cảm ơn rất nhiều vì đã trả lời câu hỏi. Tôi vẫn tò mò mặc dù tại sao một Iterable<Integer>(tôi nghĩ bạn đang nói về?) Muốn trả lại một IntStream. Lặp đi lặp lại sau đó không phải là một PrimitiveIterator.OfInt? Hay bạn có thể có nghĩa là một usecase khác?
skiwi

139
Tôi thấy thật kỳ lạ khi logic trên được cho là áp dụng cho Iterable (tôi không thể có luồng () vì ai đó có thể muốn nó trả lại IntStream) trong khi một lượng suy nghĩ tương đương không được đưa vào để thêm phương thức chính xác vào Bộ sưu tập ( Tôi có thể muốn luồng Bộ sưu tập <Integer> của tôi cũng trả lại IntStream.) Cho dù nó có mặt trên cả hai hoặc vắng mặt trên cả hai, mọi người có thể đã tiếp tục với cuộc sống của họ, nhưng vì nó hiện diện trên một và vắng mặt mặt khác, nó trở thành một thiếu sót rõ ràng ...
Trejkaz

6
Brian McCutchon: Điều đó có ý nghĩa hơn với tôi. Có vẻ như mọi người đã mệt mỏi với việc tranh luận và quyết định chơi nó an toàn.
Jonathan Locke

44
Mặc dù điều này có ý nghĩa, nhưng có một lý do tại sao không có tĩnh thay thế Stream.of(Iterable), ít nhất sẽ làm cho phương thức có thể khám phá hợp lý bằng cách đọc tài liệu API - vì một người chưa bao giờ thực sự làm việc với các luồng bên trong tôi không bao giờ thậm chí nhìn vào StreamSupport, được mô tả trong tài liệu là cung cấp "các hoạt động cấp thấp" mà "chủ yếu dành cho các nhà văn thư viện".
Jules

9
Tôi hoàn toàn đồng ý với Jules. nên thêm một phương thức tĩnh Stream.of (Iterizable iter) hoặc Stream.of (Iterator iter), thay vì StreamSupport.stream (iter.spliterator (), false);
user_3380739

23

Tôi đã làm một cuộc điều tra trong một số danh sách gửi thư lambda của dự án và tôi nghĩ rằng tôi đã tìm thấy một vài cuộc thảo luận thú vị.

Tôi đã không tìm thấy một lời giải thích thỏa đáng cho đến nay. Sau khi đọc tất cả những điều này tôi đã kết luận đó chỉ là một thiếu sót. Nhưng bạn có thể thấy ở đây rằng nó đã được thảo luận nhiều lần trong nhiều năm trong quá trình thiết kế API.

Chuyên gia kỹ thuật Lambda Libs

Tôi tìm thấy một cuộc thảo luận về điều này trong danh sách gửi thư của Chuyên gia Lambda Libs :

Trong Iterable / Iterator.stream () Sam Pullara đã nói:

Tôi đã làm việc với Brian để xem cách giới hạn chức năng / giới hạn [1] có thể được thực hiện và anh ấy đề nghị chuyển đổi sang Iterator là cách phù hợp để thực hiện. Tôi đã nghĩ về giải pháp đó nhưng không tìm thấy bất kỳ cách rõ ràng nào để lấy một trình vòng lặp và biến nó thành một luồng. Hóa ra nó ở trong đó, trước tiên bạn chỉ cần chuyển đổi iterator thành một spliterator và sau đó chuyển đổi spliterator thành một luồng. Vì vậy, điều này mang lại cho tôi để xem xét lại liệu chúng ta nên có những cái này treo trực tiếp một trong số Iterable / Iterator hoặc cả hai.

Đề nghị của tôi là ít nhất có nó trên Iterator để bạn có thể di chuyển sạch sẽ giữa hai thế giới và nó cũng có thể dễ dàng khám phá hơn là phải làm:

Streams.stream (Spliterators.spliteratorUn UnknownSize (iterator, Spliterator.ORDERED))

Và sau đó Brian Goetz trả lời :

Tôi nghĩ rằng quan điểm của Sam là có rất nhiều lớp thư viện cung cấp cho bạn một Iterator nhưng đừng để bạn nhất thiết phải viết bộ chia của riêng bạn. Vì vậy, tất cả những gì bạn có thể làm là gọi luồng (spliteratorUn UnknownSize (iterator)). Sam đang đề nghị chúng tôi xác định Iterator.stream () để làm điều đó cho bạn.

Tôi muốn giữ các phương thức stream () và spliterator () như dành cho người viết thư viện / người dùng nâng cao.

Và sau đó

"Cho rằng việc viết Spliterator dễ hơn viết Iterator, tôi thích chỉ viết Spliterator thay vì Iterator (Iterator là 90s :)"

Bạn đang thiếu điểm, mặc dù. Có hàng trăm lớp học ngoài đó đã trao cho bạn một Iterator. Và nhiều trong số chúng không sẵn sàng.

Thảo luận trước đây trong danh sách gửi thư Lambda

Đây có thể không phải là câu trả lời bạn đang tìm kiếm nhưng trong danh sách gửi thư của Dự án Lambda, điều này đã được thảo luận ngắn gọn. Có lẽ điều này giúp thúc đẩy một cuộc thảo luận rộng hơn về chủ đề này.

Theo lời của Brian Goetz trong Luồng từ Iterable :

Lùi lại...

Có rất nhiều cách để tạo Stream. Bạn càng có nhiều thông tin về cách mô tả các yếu tố, thư viện luồng có thể cung cấp cho bạn càng nhiều chức năng và hiệu suất. Theo thứ tự ít nhất cho hầu hết các thông tin, chúng là:

Lặp lại

Lặp lại + kích thước

Bộ chia tín hiệu

Spliterator biết kích thước của nó

Bộ chia tín hiệu biết kích thước của nó và biết thêm rằng tất cả các phân chia phụ đều biết kích thước của chúng.

(Một số người có thể ngạc nhiên khi thấy rằng chúng ta có thể trích xuất song song ngay cả từ một trình lặp câm trong trường hợp Q (công việc trên mỗi phần tử) là không cần thiết.)

Nếu Iterable có phương thức stream (), nó sẽ chỉ bọc Iterator bằng Spliterator, không có thông tin kích thước. Nhưng, hầu hết những thứ có thể lặp lại đều có thông tin kích thước. Điều đó có nghĩa là chúng tôi đang phục vụ các luồng thiếu. Điều đó không tốt lắm.

Một nhược điểm của thực tiễn API được Stephen nêu ra ở đây, khi chấp nhận Iterable thay vì Collection, là bạn đang buộc mọi thứ thông qua một "ống nhỏ" và do đó loại bỏ thông tin kích thước khi nó có thể hữu ích. Điều đó tốt nếu tất cả những gì bạn đang làm là bỏ qua nó, nhưng nếu bạn muốn làm nhiều hơn, sẽ tốt hơn nếu bạn có thể lưu giữ tất cả thông tin bạn muốn.

Mặc định được cung cấp bởi Iterable sẽ thực sự là một thứ vớ vẩn - nó sẽ loại bỏ kích thước mặc dù đại đa số các Iterables đều biết thông tin đó.

Mâu thuẫn?

Mặc dù, có vẻ như cuộc thảo luận dựa trên những thay đổi mà Nhóm chuyên gia đã thực hiện đối với thiết kế ban đầu của Luồng ban đầu dựa trên các trình vòng lặp.

Mặc dù vậy, thật thú vị khi nhận thấy rằng trong một giao diện như Collection, phương thức stream được định nghĩa là:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

Đó có thể là chính xác cùng mã được sử dụng trong giao diện Iterable.

Vì vậy, đây là lý do tại sao tôi nói câu trả lời này có thể không thỏa đáng, nhưng vẫn thú vị cho cuộc thảo luận.

Bằng chứng tái cấu trúc

Tiếp tục với phân tích trong danh sách gửi thư, có vẻ như phương thức splitIterator ban đầu có trong giao diện Bộ sưu tập, và tại một số điểm vào năm 2013, họ đã chuyển nó lên Iterable.

Kéo splitIterator lên từ Collection sang Iterable .

Kết luận / Lý thuyết?

Sau đó, rất có thể việc thiếu phương thức trong Iterable chỉ là một thiếu sót, vì có vẻ như họ cũng nên chuyển phương thức stream khi họ chuyển splitIterator lên từ Collection sang Iterable.

Nếu có những lý do khác không rõ ràng. Ai đó có lý thuyết khác?


Tôi đánh giá cao phản ứng của bạn, nhưng tôi không đồng ý với lý do ở đó. Tại thời điểm đó bạn ghi đè lên spliterator()các Iterable, sau đó tất cả các vấn đề có được cố định, và bạn trivially có thể thực hiện stream()parallelStream()..
skiwi

@skiwi Đó là lý do tại sao tôi nói điều này có lẽ không phải là câu trả lời. Tôi chỉ cố gắng thêm vào cuộc thảo luận, bởi vì thật khó để biết tại sao nhóm chuyên gia đưa ra quyết định mà họ đã làm. Tôi đoán tất cả những gì chúng ta có thể làm là cố gắng thực hiện một số pháp y trong danh sách gửi thư và xem liệu chúng ta có thể đưa ra bất kỳ lý do nào không.
Edwin Dalorzo

1
@skiwi Tôi đã xem xét các danh sách gửi thư khác và tìm thấy nhiều bằng chứng cho cuộc thảo luận và có lẽ một số ý tưởng giúp đưa ra lý thuyết về một số chẩn đoán.
Edwin Dalorzo

Cảm ơn những nỗ lực của bạn, tôi thực sự nên học cách phân chia hiệu quả thông qua các danh sách gửi thư đó. Sẽ rất hữu ích nếu chúng có thể được hình dung theo một cách ... hiện đại nào đó, như một diễn đàn hoặc một cái gì đó, bởi vì đọc email văn bản đơn giản với các trích dẫn trong đó không thực sự hiệu quả.
skiwi

6

Nếu bạn biết kích thước bạn có thể sử dụng java.util.Collectionsẽ cung cấp stream()phương thức:

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

Và sau đó:

new Hand().stream().map(...)

Tôi đã đối mặt với cùng một vấn đề và ngạc nhiên rằng Iterableviệc triển khai của tôi có thể rất dễ dàng được mở rộng thành AbstractCollectionviệc thực hiện bằng cách thêm size()phương thức (may mắn là tôi có kích thước của bộ sưu tập :-)

Bạn cũng nên xem xét để ghi đè Spliterator<E> spliterator().

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.