5

ここでJava ストリーム スプリッテレータを深く理解することに関する最初の質問の後、ストリームに関する別の微妙な質問: なぜ.flatMap()Java での実装は非常に非効率的 (非遅延) なのですか?

通常、ストリームは可能な限り遅延する必要がありますが、.flatMap()メソッドはそうではありません。

例えば:

stream.flatMap(this::getStreamWith10HeavyComputationElems).firstFirst()最初の重い計算結果を返す前に、10 要素 (10 回の重い計算) を消費します。

stream.flatMap(this::getStreamWith10HeavyComputationElems).limit(11).count()11 を返す前に 20 要素 (2x10 の重い計算) を消費します。

問題は、Java が非遅延実装を使用する理由です。

    @Test
    void flatMap_native() throws Exception {
        AtomicInteger count = new AtomicInteger();
        Stream<Long> stream = LongStream.range(0, 5).boxed()
                .flatMap(num -> LongStream.range(0, 10).boxed()
                                    .peek(x -> count.incrementAndGet()))
                .limit(11);

        assertThat(stream).hasSize(11);
        assertThat(count).hasValue(20); //!why? - should be 11!
    }

回避策として、flatMap の独自の実装を作成しましたが、ネイティブの呼び出し: flatMap(stream, mapper)vs nativeと比較して流暢さに欠けstream.flatMap(mapper)ます。

public static <T, R> Stream<R> flatMap(Stream<? extends T> stream, Function<? super T, ? extends Stream<? extends R>> mapper) {
    // Outside the class to be able to close it, starts with stream.empty
    AtomicReference<Stream<? extends R>> flatMapStreamRef = new AtomicReference<>(Stream.empty());

    // Defining a better spliterator than the native flatMap one.
    class FlatMapSpliterator implements Spliterator<R> {
        private final AtomicReference<T> item = new AtomicReference<>();
        private final Spliterator<? extends T> spliterator;
        private Stream<? extends R> flatMapStream = flatMapStreamRef.get();
        private Spliterator<? extends R> flatMapSpliterator = flatMapStream.spliterator();

        private FlatMapSpliterator(Spliterator<? extends T> spliterator) {
            this.spliterator = spliterator;
        }

        @Override
        public boolean tryAdvance(Consumer<? super R> action) {
            while(true) {
                if (flatMapSpliterator.tryAdvance(action)) {
                    return true;
                }
                if (!spliterator.tryAdvance(item::set)) {
                    return false; // nothing more to process
                }
                Stream<? extends R> stream = mapper.apply(item.get());
                if(stream != null) {
                    flatMapStream.close();
                    flatMapStream = stream;
                    flatMapStreamRef.set(stream);
                    flatMapSpliterator = flatMapStream.spliterator();
                }
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public Spliterator<R> trySplit() {
            Spliterator<? extends R> subFlatMapSpliterator = flatMapSpliterator.trySplit();
            if(subFlatMapSpliterator != null) {
                return (Spliterator<R>) subFlatMapSpliterator;
            }

            Spliterator<? extends T> subSpliterator = spliterator.trySplit();
            if(subSpliterator == null) {
                return null;
            }

            return new FlatMapSpliterator(subSpliterator);
        }

        @Override
        public long estimateSize() {
            // If both estimate size are Long.MAX_VALUE then math overflow will happen
            long estimateSize = spliterator.estimateSize() + flatMapSpliterator.estimateSize();
            return estimateSize < 0 ? Long.MAX_VALUE : estimateSize;
        }

        @Override
        public int characteristics() {
            // Maintain only ORDERED (used by native flatMap)
            return spliterator.characteristics() & ORDERED;
        }
    }

    return StreamSupport.stream(new FlatMapSpliterator(stream.spliterator()), stream.isParallel())
            .onClose(stream::close)
            .onClose(flatMapStreamRef.get()::close);
}
4

0 に答える 0