Tại sao Stream <T> không triển khai lặp lại <T>?


262

Trong Java 8, chúng ta có lớp Stream <T> , có một phương thức tò mò

Iterator<T> iterator()

Vì vậy, bạn sẽ mong đợi nó triển khai giao diện Iterable <T> , yêu cầu chính xác phương pháp này, nhưng thực tế không phải vậy.

Khi tôi muốn lặp lại một luồng bằng vòng lặp foreach, tôi phải làm một cái gì đó như

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Am i thiếu cái gì ở đây?


7
không đề cập đến việc 2 phương thức lặp khác (forEach và spliterator) cũng nằm trong Stream
njzk2

1
điều này là cần thiết để chuyển Streamđến các API kế thừa mong đợiIterable
ZhongYu

12
Một IDE tốt (ví dụ như IntelliJ) sẽ nhắc bạn để đơn giản hóa mã của bạn trong getIterable()đểreturn s::iterator;
Zhongyu

24
Bạn không cần một phương pháp nào cả. Nơi bạn có Luồng và muốn có một Vòng lặp, chỉ cần truyền luồng :: iterator (hoặc, nếu bạn thích, () -> stream.iterator ()), và bạn đã hoàn thành.
Brian Goetz

7
Thật không may, tôi không thể viết for (T element : stream::iterator), vì vậy tôi vẫn thích nếu Stream cũng sẽ thực hiện Iterablehoặc một phương thức toIterable().
Thorsten

Câu trả lời:


197

Mọi người đã hỏi tương tự trong danh sách gửi thư . Lý do chính là Iterable cũng có một ngữ nghĩa lặp lại, trong khi Stream thì không.

Tôi nghĩ lý do chính là Iterablengụ ý tái sử dụng, trong khi đó Streamlà thứ chỉ có thể được sử dụng một lần - giống như một Iterator.

Nếu được Streammở rộng Iterablethì mã hiện tại có thể gây ngạc nhiên khi nó nhận được một lần Iterableném Exceptionthứ hai for (element : iterable).


22
Thật kỳ lạ, đã có một số lần lặp với hành vi này trong Java 7, ví dụ DirectoryStream: Trong khi DirectoryStream mở rộng Iterable, nó không phải là Iterable có mục đích chung vì nó chỉ hỗ trợ một Iterator duy nhất; gọi phương thức iterator để có được một iterator thứ hai hoặc tiếp theo ném IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/ mẹo )
roim

32
Thật không may, không có gì là tài liệu Iterablevề việc iteratornên luôn luôn hoặc có thể không thể gọi được nhiều lần. Đó là thứ họ nên đặt vào đó. Đây dường như là một thực hành tiêu chuẩn hơn là một đặc điểm kỹ thuật chính thức.
Lii

7
Nếu họ sẽ sử dụng các lý do, bạn sẽ nghĩ rằng ít nhất họ có thể thêm một phương thức asIterable () - hoặc quá tải cho tất cả các phương thức chỉ sử dụng Iterable.
Trejkaz

26
Có lẽ giải pháp tốt nhất có thể đã khiến cho các foreach của Java chấp nhận <T> lặp lại cũng như có khả năng là một luồng <T>?
nuốt chửng elysium

3
@Lii Khi thực hành tiêu chuẩn, đó là một cách khá mạnh mẽ.
biziclop

161

Để chuyển đổi Streamthành một Iterable, bạn có thể làm

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Để vượt qua một Streamphương pháp mong đợi Iterable,

void foo(Iterable<X> iterable)

đơn giản

foo(stream::iterator) 

tuy nhiên nó có vẻ buồn cười; có thể tốt hơn để rõ ràng hơn một chút

foo( (Iterable<X>)stream::iterator );

67
Bạn cũng có thể sử dụng điều này trong một vòng lặp for(X x : (Iterable<X>)stream::iterator), mặc dù nó trông xấu xí. Thực sự, toàn bộ tình hình chỉ là vô lý.
Alexanderr Dubinsky

24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay

11
Tôi không hiểu cú pháp dấu hai chấm trong ngữ cảnh này. Sự khác biệt giữa stream::iteratorstream.iterator(), điều đó làm cho cái trước chấp nhận được Iterablenhưng không phải cái sau?
Daniel C. Sobral

20
Trả lời bản thân: Iterablelà một giao diện chức năng, vì vậy việc truyền một chức năng thực hiện nó là đủ.
Daniel C. Sobral

5
Tôi phải đồng ý với @AleksandrDubinsky, Vấn đề thực sự là có một cách giải quyết tương đối cho (X x: (Iterable <X>) stream :: iterator), chỉ cho phép hoạt động tương tự mặc dù có tiếng ồn mã lớn. Nhưng sau đó, đây là Java và chúng tôi đã chấp nhận Danh sách <String> list = new ArrayList (Arrays.asList (mảng)) trong một thời gian dài :) Mặc dù các quyền hạn có thể cung cấp cho chúng tôi tất cả Danh sách <String> list = mảng. toArrayList (). Nhưng điều phi lý thực sự, là bằng cách khuyến khích mọi người sử dụng forEach trên Stream, các ngoại lệ nhất thiết phải được bỏ qua [kết thúc rant].
Điều phối viên

10

Tôi muốn chỉ ra rằng StreamExviệc thực hiện Iterable(và Stream), cũng như một loạt các chức năng vô cùng tuyệt vời khác bị thiếu Stream.


8

Bạn có thể sử dụng Luồng trong một forvòng lặp như sau:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Chạy đoạn trích này tại đây )

(Cái này sử dụng giao diện chức năng Java 8.)

(Điều này được đề cập trong một số ý kiến ​​ở trên (ví dụ: Alexanderr Dubinsky ), nhưng tôi muốn rút nó ra thành một câu trả lời để làm cho nó rõ hơn.)


Hầu như tất cả mọi người cần phải nhìn vào nó hai lần hoặc ba lần trước khi họ nhận ra nó thậm chí còn biên dịch như thế nào. Thật là vô lý. (Điều này được đề cập trong phản hồi cho các bình luận trong cùng một luồng bình luận, nhưng tôi muốn thêm bình luận ở đây để làm cho nó rõ hơn.)
ShioT

7

kennytm đã mô tả lý do tại sao không an toàn khi coi Streamnhư một Iterable, và Zhong Yu đưa ra một cách giải quyết cho phép sử dụng Streamnhư trong Iterable, mặc dù theo cách không an toàn. Có thể có được điều tốt nhất của cả hai thế giới: có thể tái sử dụng Iterabletừ một Streamthế giới đáp ứng tất cả các đảm bảo được thực hiện bởi Iterablethông số kỹ thuật.

Lưu ý: SomeTypekhông phải là một tham số loại ở đây - bạn cần thay thế nó bằng một loại thích hợp (ví dụ String:) hoặc dùng đến sự phản chiếu

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Có một nhược điểm lớn:

Những lợi ích của việc lặp lại lười biếng sẽ bị mất. Nếu bạn dự định lặp lại ngay lập tức tất cả các giá trị trong luồng hiện tại, mọi chi phí sẽ không đáng kể. Tuy nhiên, nếu bạn dự định chỉ lặp lại một phần hoặc trong một luồng khác, việc lặp lại ngay lập tức và hoàn chỉnh này có thể có những hậu quả không lường trước được.

Tất nhiên, lợi thế lớn là bạn có thể sử dụng lại Iterable, trong khi (Iterable<SomeType>) stream::iteratorchỉ cho phép sử dụng một lần. Nếu mã nhận sẽ lặp đi lặp lại trên bộ sưu tập nhiều lần, điều này không chỉ cần thiết mà còn có lợi cho hiệu suất.


1
Bạn đã thử biên dịch mã của mình trước khi trả lời chưa? Nó không hoạt động.
Tagir Valeev

1
@TagirValeev Vâng, tôi đã làm. Bạn cần thay T bằng loại thích hợp. Tôi đã sao chép ví dụ từ mã làm việc.
Zenexer

2
@TagirValeev Tôi đã thử nghiệm lại trong IntelliJ. Có vẻ như IntelliJ đôi khi bị nhầm lẫn bởi cú pháp này; Tôi đã không thực sự tìm thấy một mô hình cho nó. Tuy nhiên, mã biên dịch tốt và IntelliJ xóa thông báo lỗi sau khi biên dịch. Tôi đoán đó chỉ là một lỗi.
Zenexer

1
Stream.toArray()trả về một mảng, không phải là một Iterable, vì vậy mã này vẫn không biên dịch. nhưng nó có thể là một lỗi trong nhật thực, vì IntelliJ dường như biên dịch nó
benez

1
@Zenexer Làm thế nào bạn có thể gán một mảng cho một Iterable?
radiantRazor

3

Streamkhông thực hiện Iterable. Sự hiểu biết chung về Iterablebất cứ điều gì có thể được lặp đi lặp lại, thường xuyên lặp đi lặp lại. Streamcó thể không được phát lại.

Cách giải quyết duy nhất mà tôi có thể nghĩ đến, trong đó một lần lặp dựa trên luồng cũng có thể phát lại được, là tạo lại luồng. Tôi đang sử dụng một Supplierbên dưới để tạo một phiên bản mới của luồng, mỗi khi một trình lặp mới được tạo.

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }

2

Nếu bạn không phiền khi sử dụng thư viện của bên thứ ba, cyclops-Reac sẽ xác định Luồng thực hiện cả Luồng và Iterable và cũng có thể phát lại được (giải quyết vấn đề kennytm được mô tả ).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

hoặc là :-

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Tiết lộ Tôi là nhà phát triển chính của cyclops-Reac]


0

Không hoàn hảo, nhưng sẽ hoạt động:

iterable = stream.collect(Collectors.toList());

Không hoàn hảo bởi vì nó sẽ tìm nạp tất cả các mục từ luồng và đặt chúng vào đó List, đó không phải là chính xác những gì IterableStreamvề. Họ được cho là lười biếng .


-1

Bạn có thể lặp lại tất cả các tệp trong một thư mục bằng cách sử dụng Stream<Path>như sau:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}
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.