【问题标题】:Java 8 Spliterator (or similar) that returns a value iff there's only a single valueJava 8 Spliterator(或类似的),如果只有一个值,则返回一个值
【发布时间】:2015-01-04 21:14:57
【问题描述】:

我是singleOrEmpty 流操作符的忠实粉丝。它不在标准库中,但我发现它非常有用。如果流只有一个值,它会在 Optional 中返回该值。如果它没有值或多个值,则返回Optional.empty()。

Optional<Int> value = someList.stream().{singleOrEmpty}
[]     -> Optional.empty()
[1]    -> Optional.of(1)
[1, 1] -> Optional.empty()
etc.

I asked a question about it earlier 和 @ThomasJungblut came up with this great implementation:

public static <T> Optional<T> singleOrEmpty(Stream<T> stream) {
    return stream.limit(2)
        .map(Optional::ofNullable)
        .reduce(Optional.empty(),
             (a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
}

唯一的问题是,你必须把它放在通话的开头

singleOrEmpty(someList.stream().filter(...).map(...))

而不是在最后依次

someList.stream().filter().map().singleOrEmpty()

这使得它比其他流机制更难阅读。

所以作为流处理的新手,有没有人知道如何将短路singleOrEmpty 机制放在流转换序列的结束?

【问题讨论】:

标签: java java-8 java-stream spliterator


【解决方案1】:

它不会像有限制(2)的那样快,但你可以像list.stream().filter(...).map(...).collect(singleOrEmpty())那样使用它

static <T> Collector<T, ?, Optional<T>> singleOrEmpty() {
    return Collectors.collectingAndThen(
            Collectors.mapping(
                    Optional::of,
                    Collectors.reducing((a, b) -> Optional.empty())
            ),
            o -> o.orElseGet(Optional::empty)
    );
}

Stream.empty().collect(singleOrEmpty());   // Optional.empty
Stream.of(1).collect(singleOrEmpty());     // Optional[1]
Stream.of(1, 1).collect(singleOrEmpty());  // Optional.empty
Stream.of(1, 1).skip(1).collect(singleOrEmpty());  // Optional[1]

对于它的价值,除非这确实是性能关键代码,否则我个人更喜欢不太聪明但更清晰的实现

static<T> Collector<T,?,Optional<T>> singleOrEmpty() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            lst -> lst.size() == 1
                    ? Optional.of(lst.get(0))
                    : Optional.empty()
    );
}

【讨论】:

  • 第一个收集器可以简化为Collectors.reducing(...) 的重载版本。我认为这个版本并不比你建议的第二个版本差(对于大段代码感到抱歉,我只是认为不值得单独回答)。 static &lt;T&gt; Collector&lt;T, ?, Optional&lt;T&gt;&gt; singleOrEmptyCollector() { return Collectors.reducing( Optional.empty(), Optional::ofNullable, (opt, t) -&gt; opt.isPresent() ^ t.isPresent() ? t : Optional.empty() ); }
  • 另外,还有一点要提。如果管道有一些昂贵的操作(如map((a) -&gt; doSomethingReallySlow(a))),那么即使我们只想要一个,我们仍然必须在所有元素上执行它。 limit(2) 可以救我们,但不是在所有情况下。有关详细信息,请参阅limit documentation。所以要小心,不要指望这个收集器会跳过除一个以外的所有元素(或者至少像stream.of(...).limit(2).collect(singleOrEmpty()) 一样使用它)。
  • @StanislavLukyanov 如果流有 3 个(或 5、7 等)元素,您的收集器将失败。例如,Stream.of(1,1,1).collect(...) 将返回 Optional[1] 而不是 Optional.empty。
  • 谢谢!你说得对!实际上,我想出了更简单的技巧:Collectors.reducing((a, b) -&gt; null)。希望这次我没有错过任何东西:)
  • reducing((a,b) -&gt; null) 有效!但是感觉太不对劲了。 stream.reduce 如果归约结果为 null 但 Collectors.reducing 没有,则抛出 NPE。我想知道这是故意的还是疏忽。使用此技巧的一个缺点是由单个 null 组成的流将显示为空。也许更强大的解决方案是mapping(Objects::requireNonNull, reducing( (a,b) -&gt; null))。
猜你喜欢
  • 2015-01-04
  • 2018-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
相关资源
最近更新 更多