Java 8 收集器,如果只有一个值,则返回一个值

2022-09-01 09:39:21

我对这个函数式编程和流的东西有点绿色,但我所知道的一点点非常有用!

这种情况出现过好几次:

List<SomeProperty> distinctProperties = someList.stream()
    .map(obj -> obj.getSomeProperty())
    .distinct()
    .collect(Collectors.toList());

if (distinctProperties.size() == 1) {
    SomeProperty commonProperty = distinctProperties.get(0);
    // take some action knowing that all share this common property
}

我真正想要的是:

Optional<SomeProperty> universalCommonProperty = someList.stream()
    .map(obj -> obj.getSomeProperty())
    .distinct()
    .collect(Collectors.singleOrEmpty());

我认为除了与.当我还是一个uber n00b的时候,我花了很多时间重新发明Java Collections Framework,因为我不知道它在那里,所以我尽量不重复我的错误。Java有没有一个很好的方法来做这件事?我是不是说错了?singleOrEmptydistinctsingleOrEmpty

谢谢!

编辑:这是该案例的一些示例数据。如果忽略该步骤:distinctmap

Optional<SomeProperty> universalCommonProperty = someList.stream()
    .map(obj -> obj.getSomeProperty())
    .distinct()
    .collect(Collectors.singleOrEmpty());

[]     -> Optional.empty()
[1]    -> Optional.of(1)
[1, 1] -> Optional.of(1)
[2, 2] -> Optional.of(2)
[1, 2] -> Optional.empty()

我发现当我搞砸了我的类型或有遗留代码时,我需要这个。能够快速说出“此集合的所有元素共享此属性,因此现在我可以使用此共享属性执行一些操作”,这真是太好了。另一个例子是,当用户多选一些不同的元素时,你试图看看你可以做哪些东西(如果有的话)对所有元素都有效。

编辑2:对不起,如果我的例子是误导性的。关键是单或空。我通常发现我在前面放了一个,但它可以很容易地成为其他类型。distinctfilter

Optional<SomeProperty> loneSpecialItem = someList.stream()
    .filter(obj -> obj.isSpecial())
    .collect(Collectors.singleOrEmpty());

[special]           -> Optional.of(special)
[special, special]  -> Optional.empty()
[not]               -> Optional.empty()
[not, special]      -> Optional.of(special)
[not, special, not] -> Optional.of(special)

编辑3:我认为我通过激励单身OrEmpty而不是仅仅要求它本身而搞砸了。

Optional<Int> value = someList.stream().collect(Collectors.singleOrEmpty())
[]     -> Optional.empty()
[1]    -> Optional.of(1)
[1, 1] -> Optional.empty()

答案 1

这将产生创建集合的开销,但它很简单,即使您忘记先区分()流,也可以正常工作。

static<T> Collector<T,?,Optional<T>> singleOrEmpty() {
    return Collectors.collectingAndThen(
            Collectors.toSet(),
            set -> set.size() == 1 
                    ? set.stream().findAny() 
                    : Optional.empty()
    );
}

答案 2

“Hacky”解决方案,仅评估前两个元素:

    .limit(2)
    .map(Optional::ofNullable)
    .reduce(Optional.empty(),
        (a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());

一些基本解释:

单元素 [1] ->映射到 [可选(1)] -> reduce

"Empty XOR Present" yields Optional(1)

= 可选(1)

两个元素 [1, 2] ->映射到 [可选(1), 可选(2)] -> reduce 执行:

"Empty XOR Present" yields Optional(1)
"Optional(1) XOR Optional(2)" yields Optional.Empty

= 可选。空

以下是完整的测试用例:

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());
}

@Test
public void test() {
    testCase(Optional.empty());
    testCase(Optional.of(1), 1);
    testCase(Optional.empty(), 1, 1);
    testCase(Optional.empty(), 1, 1, 1);
}

private void testCase(Optional<Integer> expected, Integer... values) {
    Assert.assertEquals(expected, singleOrEmpty(Arrays.stream(values)));
}

向Ned(OP)致敬,他贡献了XOR的想法和上面的测试用例!


推荐