我正在过滤流,但如果过滤器没有返回任何匹配项,我想返回一个默认值。这是在一系列额外的流中,所以如果一步没有任何结果,我正在使用它来避免链停止。
目前我通过将过滤器的结果收集到列表中来捏造它,如果列表为空,则创建我的新默认列表并将其作为流返回。如果列表不为空,请将结果转换回流以将其传回。
什么是更流畅的方式来实现这一目标而无需前往列表并返回流中?
答案 0 :(得分:2)
避免收集整个流的最佳解决方案,避免丢失原始Stream
特征并保持(大部分)其优化是实现处理默认值的自定义Spliterator
case原始流的Spliterator
为空:
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]
答案 1 :(得分:1)
根据我的理解,您需要一种类似于C#中的DefaultIfEmpty的方法。不幸的是,Stream API没有这样的方法,但幸运的是,有人已经实现了这种方法。
从@Stuart Marks answer采用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());
}
}
例如sakes,假设你有一个整数列表:
List<Integer> integerList = new ArrayList<>(Arrays.asList(1,3,5,7,9));
如果上述列表中没有偶数,则需要具有单个值的流。
用例将是:
Stream<Integer> result = defaultIfEmpty(integerList.stream()
.filter(e -> e %2 == 0), () -> 99);
这将产生Stream<Integer>
,其中单个元素为99
,因为filter
操作返回空流。然后,您可以对从defaultIfEmpty
返回的流进行进一步的操作,即
defaultIfEmpty(integerList.stream()
.filter(e -> e %2 == 0), () -> 99)
.map(x -> x*2)
...
...
或应用后续defaultIfEmpty
方法:
Stream<Integer> result = defaultIfEmpty(
defaultIfEmpty(integerList.stream()
.filter(e -> e %2 == 0),
() -> 99).map(x -> x* 2)..., ()-> -1);
此时,您可能会意识到可读性正在丢失,并且在您进行进一步操作时仍将如此。
然而,这是最好的方法,因为当你在流上编写越来越多的方法时,我无法想到任何其他方法来实现这一点,同时保持良好的可读性。