Làm thế nào để chuyển đổi một iterator thành một luồng?


468

Tôi đang tìm kiếm một cách ngắn gọn để chuyển đổi Iteratormột Streamhoặc cụ thể hơn để "xem" trình vòng lặp dưới dạng luồng.

Vì lý do hiệu suất, tôi muốn tránh một bản sao của trình lặp trong danh sách mới:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Collection<String> copyList = new ArrayList<String>();
sourceIterator.forEachRemaining(copyList::add);
Stream<String> targetStream = copyList.stream();

Dựa trên một số gợi ý trong các bình luận, tôi cũng đã thử sử dụng Stream.generate:

public static void main(String[] args) throws Exception {
    Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
    Stream<String> targetStream = Stream.generate(sourceIterator::next);
    targetStream.forEach(System.out::println);
}

Tuy nhiên, tôi nhận được một NoSuchElementException(vì không có lời mời hasNext)

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)
    at Main$$Lambda$1/1175962212.get(Unknown Source)
    at java.util.stream.StreamSpliterators$InfiniteSupplyingSpliterator$OfRef.tryAdvance(StreamSpliterators.java:1351)
    at java.util.Spliterator.forEachRemaining(Spliterator.java:326)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at Main.main(Main.java:20)

Tôi đã xem StreamSupportCollectionstôi không tìm thấy gì cả.



3
@DmitryGinzburg euh tôi không muốn tạo một luồng "Vô hạn".
Gontard

1
@DmitryGinzburg Stream.generate(iterator::next)hoạt động?
Gontard

1
@DmitryGinzburg Điều đó sẽ không làm việc cho một trình vòng lặp hữu hạn.
assylias

Câu trả lời:


543

Một cách là tạo Spliterator từ Iterator và sử dụng nó làm cơ sở cho luồng của bạn:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();
Stream<String> targetStream = StreamSupport.stream(
          Spliterators.spliteratorUnknownSize(sourceIterator, Spliterator.ORDERED),
          false);

Một cách khác có thể dễ đọc hơn là sử dụng Iterable - và tạo Iterable từ Iterator rất dễ dàng với lambdas vì Iterable là giao diện chức năng:

Iterator<String> sourceIterator = Arrays.asList("A", "B", "C").iterator();

Iterable<String> iterable = () -> sourceIterator;
Stream<String> targetStream = StreamSupport.stream(iterable.spliterator(), false);

26
Luồng rất lười biếng: mã chỉ liên kết Luồng với Trình vòng lặp nhưng việc lặp thực tế sẽ không xảy ra cho đến khi hoạt động của thiết bị đầu cuối. Nếu bạn sử dụng iterator trong thời gian đó, bạn sẽ không nhận được kết quả như mong đợi. Ví dụ: bạn có thể giới thiệu sourceIterator.next()trước khi sử dụng luồng và bạn sẽ thấy hiệu ứng (mục đầu tiên sẽ không được xem bởi Luồng).
assylias

9
@assylias, vâng, nó thực sự tốt đẹp! Có lẽ bạn có thể giải thích cho độc giả tương lai dòng khá kỳ diệu này Iterable<String> iterable = () -> sourceIterator;. Tôi phải thừa nhận rằng tôi phải mất một thời gian để hiểu.
Gontard

7
Tôi nên nói những gì tôi tìm thấy. Iterable<T>là một FunctionalInterfacetrong đó chỉ có một phương pháp trừu tượng iterator(). Vì vậy, () -> sourceIteratormột biểu thức lambda khởi tạo một Iterablethể hiện như là một triển khai ẩn danh.
Jin Kwon

13
Một lần nữa, () -> sourceIterator;là một dạng rút gọn củanew Iterable<>() { @Override public Iterator<String> iterator() { return sourceIterator; } }
Jin Kwon

7
@JinKwon Nó không thực sự là một dạng rút gọn của một lớp ẩn danh (có một vài khác biệt tinh tế, chẳng hạn như phạm vi và cách nó được biên dịch) nhưng nó hoạt động tương tự trong trường hợp này.
assylias

122

Kể từ phiên bản 21, thư viện Guava cung cấp Streams.stream(iterator)

Nó thực hiện những gì câu trả lời của @ assylias cho thấy .



Tốt hơn nhiều để sử dụng điều này một cách nhất quán cho đến khi JDK hỗ trợ một lớp lót riêng. Sẽ đơn giản hơn nhiều để tìm thấy (do đó tái cấu trúc) điều này trong tương lai so với các giải pháp JDK thuần túy được hiển thị ở nơi khác.
drekbour

Điều này thật tuyệt vời nhưng Java làm thế nào để Java có các trình lặp và luồng bản địa nhưng không có cách đơn giản, tích hợp để đi từ cái này sang cái khác!? Theo tôi thì khá thiếu sót.
Dan Lenski

92

Đề nghị tuyệt vời! Đây là tái sử dụng của tôi trên nó:

public class StreamUtils {

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
        return asStream(sourceIterator, false);
    }

    public static <T> Stream<T> asStream(Iterator<T> sourceIterator, boolean parallel) {
        Iterable<T> iterable = () -> sourceIterator;
        return StreamSupport.stream(iterable.spliterator(), parallel);
    }
}

Và cách sử dụng (đảm bảo nhập tĩnh asStream):

List<String> aPrefixedStrings = asStream(sourceIterator)
                .filter(t -> t.startsWith("A"))
                .collect(toList());

43

Điều này có thể có trong Java 9.

Stream.generate(() -> null)
    .takeWhile(x -> iterator.hasNext())
    .map(n -> iterator.next())
    .forEach(System.out::println);

1
Đơn giản, hiệu quả và không cần dùng đến phân lớp - đây sẽ là câu trả lời được chấp nhận!
martyglaubitz

1
Thật không may, những thứ này dường như không hoạt động với .parallel()các luồng. Chúng cũng xuất hiện chậm hơn một chút so với đi qua Spliterator, ngay cả để sử dụng tuần tự.
Thomas Ahle

Ngoài ra, phương thức đầu tiên sẽ xuất hiện nếu iterator trống. Phương thức thứ hai hiện tại vẫn hoạt động, nhưng nó vi phạm yêu cầu của các chức năng trong bản đồ và mất thời gian không trạng thái, vì vậy tôi ngần ngại thực hiện điều đó trong mã sản xuất.
Hans-Peter Störr

Thực sự, đây nên là một câu trả lời được chấp nhận. Mặc dù parallelcó thể là sôi nổi, sự đơn giản là tuyệt vời.
Sven

11

Tạo Spliteratortừ Iteratorviệc sử dụng Spliteratorslớp chứa nhiều hơn một hàm để tạo bộ chia, ví dụ ở đây đang sử dụng spliteratorUnknownSizecái đang lấy iterator làm tham số, sau đó tạo Stream bằng cách sử dụngStreamSupport

Spliterator<Model> spliterator = Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.NONNULL);
Stream<Model> stream = StreamSupport.stream(spliterator, false);

1
import com.google.common.collect.Streams;

và sử dụng Streams.stream(iterator):

Streams.stream(iterator)
       .map(v-> function(v))
       .collect(Collectors.toList());

-4

Sử dụng Collections.list(iterator).stream()...


6
Trong khi ngắn, điều này là rất kém.
Olivier Grégoire

2
Điều này sẽ mở ra toàn bộ iterator thành đối tượng java sau đó chuyển đổi nó thành luồng. Tôi không đề xuất nó
iec2011007

3
Điều này dường như chỉ dành cho liệt kê, không phải lặp.
john16384

1
Không phải là một câu trả lời khủng khiếp nói chung, hữu ích trong một nhúm, nhưng câu hỏi đề cập đến hiệu suất và câu trả lời không phải là hiệu suất.
Sled
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.