Java 如果流过滤条件不满足,则返回单个元素的列表';我不返回任何结果

Java 如果流过滤条件不满足,则返回单个元素的列表';我不返回任何结果,java,java-stream,Java,Java Stream,我目前正在过滤一个流,但是如果过滤器没有返回任何匹配项,我想返回一个默认值。这是在一个附加流链中,所以我使用它来避免链停止,如果一个步骤没有任何结果 目前,我正在通过将筛选结果收集到一个列表中来伪造它,如果列表为空,则创建新的默认列表并将其作为流返回。如果列表不是空的,则将结果转换回流以将其传回 什么是一种更倾向于流的方式来实现这一点而不需要转到列表并返回流?据我所知,您需要一种类似于C#的方法。不幸的是,流API没有这样的方法,但幸运的是,有人已经实现了这种类型的东西 以中的defaultIf

我目前正在过滤一个流,但是如果过滤器没有返回任何匹配项,我想返回一个默认值。这是在一个附加流链中,所以我使用它来避免链停止,如果一个步骤没有任何结果

目前,我正在通过将筛选结果收集到一个列表中来伪造它,如果列表为空,则创建新的默认列表并将其作为流返回。如果列表不是空的,则将结果转换回流以将其传回


什么是一种更倾向于流的方式来实现这一点而不需要转到列表并返回流?

据我所知,您需要一种类似于C#的方法。不幸的是,流API没有这样的方法,但幸运的是,有人已经实现了这种类型的东西

以中的
defaultIfEmpty
方法为例,用例非常简单

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        return Stream.of(supplier.get());
    }
}
或在以下情况下应用后续的
defaultIfEmpty
方法:

 Stream<Integer> result = defaultIfEmpty(
                   defaultIfEmpty(integerList.stream()
              .filter(e -> e %2 == 0), 
      () -> 99).map(x -> x* 2)..., ()-> -1);
Stream result=defaultIfEmpty(
defaultIfEmpty(integerList.stream())
.filter(e->e%2==0),
()->99).映射(x->x*2).,()->-1);
在这一点上,您可能会意识到可读性正在丢失,并且在编写以后的操作时,这种情况仍然存在


然而,这是最好的方法,因为我想不出任何其他方法来实现这一点,同时在流上编写越来越多的方法时保持良好的可读性。

这是避免收集整个流、避免丢失原始
特征并保留(大部分)的最佳解决方案它的优化是实现一个自定义的
拆分器
,在原始流的
拆分器
为空时处理默认值:

public static <E> Stream<E> defaultIfEmpty(Stream<E> source, Supplier<? extends E> other) {
    final boolean parallel = source.isParallel();
    final Spliterator<E> originalSpliterator = source.spliterator();

    // little optimization for streams of known size
    final long size = originalSpliterator.getExactSizeIfKnown();
    if (size == 0) {
        // source already reports that it is empty
        final Stream<E> defaultStream = Stream.of(other.get());
        if (parallel) {
            return defaultStream.parallel();
        } else {
            return defaultStream;
        }
    }

    final Spliterator<E> spliterator;
    if (size > 0) {
        // source already reports that it is non-empty
        spliterator = originalSpliterator;
    } else {
        // negative means unknown, so wrap the source
        spliterator = wrap(originalSpliterator, other);
    }
    return StreamSupport.stream(spliterator, parallel);
}

private static <E> Spliterator<E> wrap(final Spliterator<E> spliterator, final Supplier<? extends E> other) {
    return new Spliterator<E>() {
        boolean useOther = true;
        @Override
        public boolean tryAdvance(final Consumer<? super E> action) {
            boolean couldAdvance = spliterator.tryAdvance(action);
            if (!couldAdvance && useOther) {
                useOther = false;
                action.accept(other.get());
                return true;
            }
            useOther = false;
            return couldAdvance;
        }

        @Override
        public Spliterator<E> trySplit() {
            if (!useOther) {
                // we know the original spliterator was not empty, we will thus never need the default
                return spliterator.trySplit();
            }
            Stream.Builder<E> builder = Stream.builder();
            if (spliterator.tryAdvance(builder)) {
                useOther = false;
                return builder.build().spliterator();
            } else {
                // spliterator is empty, but we will handle it in tryAdvance
                return null;
            }
        }

        @Override
        public long estimateSize() {
            long estimate = spliterator.estimateSize();
            if (estimate == 0 && useOther) {
                estimate = 1;
            }
            return estimate;
        }

        @Override
        public int characteristics() {
            // we don't actually change any characteristic of the original spliterator
            return spliterator.characteristics();
        }
    };
}
输出:

[42]
[1, 2, 3]
[3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
[42]

很好的问题和描述,但请发布代码以更好地说明您的描述。请查看“可选”类别,这取决于您打算如何使用该流。例如,如果只处理流的第一个元素(即调用findFirst()),则可以将原始流连接到包含默认值的流。然后确保您的默认值通过过滤器。我认为这是steam创建的问题,而不是减少的问题。如果不需要懒惰到那种程度,那么使用
tryAdvance
Stream.Builder
进行探测会使实现比包装
拆分器
简单得多(类似于中的方法)。即使在包装拆分器中,
Stream.Builder
也比
AtomicReference
@Holger更有效。对于
Stream.Builder
,我会在有时间时更改它,而不是
AtomicReference
。后来我也注意到我的答案和你的答案非常相似。我想知道事实上把这两个问题合并起来是否有意义——奥米内的代码也来自斯图尔特·马克斯对这个问题的回答。
 Stream<Integer> result = defaultIfEmpty(
                   defaultIfEmpty(integerList.stream()
              .filter(e -> e %2 == 0), 
      () -> 99).map(x -> x* 2)..., ()-> -1);
public static <E> Stream<E> defaultIfEmpty(Stream<E> source, Supplier<? extends E> other) {
    final boolean parallel = source.isParallel();
    final Spliterator<E> originalSpliterator = source.spliterator();

    // little optimization for streams of known size
    final long size = originalSpliterator.getExactSizeIfKnown();
    if (size == 0) {
        // source already reports that it is empty
        final Stream<E> defaultStream = Stream.of(other.get());
        if (parallel) {
            return defaultStream.parallel();
        } else {
            return defaultStream;
        }
    }

    final Spliterator<E> spliterator;
    if (size > 0) {
        // source already reports that it is non-empty
        spliterator = originalSpliterator;
    } else {
        // negative means unknown, so wrap the source
        spliterator = wrap(originalSpliterator, other);
    }
    return StreamSupport.stream(spliterator, parallel);
}

private static <E> Spliterator<E> wrap(final Spliterator<E> spliterator, final Supplier<? extends E> other) {
    return new Spliterator<E>() {
        boolean useOther = true;
        @Override
        public boolean tryAdvance(final Consumer<? super E> action) {
            boolean couldAdvance = spliterator.tryAdvance(action);
            if (!couldAdvance && useOther) {
                useOther = false;
                action.accept(other.get());
                return true;
            }
            useOther = false;
            return couldAdvance;
        }

        @Override
        public Spliterator<E> trySplit() {
            if (!useOther) {
                // we know the original spliterator was not empty, we will thus never need the default
                return spliterator.trySplit();
            }
            Stream.Builder<E> builder = Stream.builder();
            if (spliterator.tryAdvance(builder)) {
                useOther = false;
                return builder.build().spliterator();
            } else {
                // spliterator is empty, but we will handle it in tryAdvance
                return null;
            }
        }

        @Override
        public long estimateSize() {
            long estimate = spliterator.estimateSize();
            if (estimate == 0 && useOther) {
                estimate = 1;
            }
            return estimate;
        }

        @Override
        public int characteristics() {
            // we don't actually change any characteristic of the original spliterator
            return spliterator.characteristics();
        }
    };
}
System.out.println(defaultIfEmpty(Stream.empty(), () -> 42).collect(toList()));
System.out.println(defaultIfEmpty(Stream.of(1, 2, 3), () -> 42).collect(toList()));
System.out.println(defaultIfEmpty(Stream.iterate(1, i -> i+1).parallel().filter(i -> i%3 == 0).limit(10), () -> 42).collect(toList()));
System.out.println(defaultIfEmpty(Stream.iterate(1, i -> i+1).parallel().limit(3).filter(i -> i%4 == 0), () -> 42).collect(toList()));
[42]
[1, 2, 3]
[3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
[42]