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

Ned*_*igg 32 java java-8 java-stream

我在这个函数式编程和流程方面有点绿色,但我知道的一点点非常有用!

我已经多次出现这种情况了:

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
}
Run Code Online (Sandbox Code Playgroud)

我真正想要的是:

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

我认为singleOrEmpty除了与之相结合之外,这个东西在其他情况下也很有用distinct.当我是一个超级n00b时,我花了很多时间重新发明Java Collections Framework,因为我不知道它在那里,所以我不想重复我的错误.Java是否有一个很好的方法来做这singleOrEmpty件事?我错误地制定了它吗?

谢谢!

编辑:这是distinct案例的一些示例数据.如果您忽略该map步骤:

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()
Run Code Online (Sandbox Code Playgroud)

当我搞砸了我的类型或遗留代码时,我发现我需要这个.很高兴能够快速说出"此集合的所有元素共享此属性,所以现在我可以使用此共享属性执行某些操作." 另一个例子是当用户多选一些不同的元素时,你正试图看看你能做什么(如果有的话)对所有这些元素都有效.

编辑2:对不起,如果我的例子有误导性.关键是singleOrEmpty.我经常发现我把它distinct放在前面,但它可以很容易地成为filter其他类型.

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)
Run Code Online (Sandbox Code Playgroud)

EDIT3:我认为我通过激励singleOrEmpty而不仅仅是单独要求它而搞砸了.

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

Mis*_*sha 19

这将产生创建集合的开销,但它很简单并且即使您首先忘记distinct()流也能正常工作.

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


Tho*_*lut 13

"Hacky"解决方案仅评估前两个元素:

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

一些基本解释:

单个元素 [1] - >映射到[Optional(1)] - > reduce do

"Empty XOR Present" yields Optional(1)
Run Code Online (Sandbox Code Playgroud)

=可选(1)

两个元素 [1,2] - >映射到[Optional(1),Optional(2)] - > reduce do:

"Empty XOR Present" yields Optional(1)
"Optional(1) XOR Optional(2)" yields Optional.Empty
Run Code Online (Sandbox Code Playgroud)

= 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)));
}
Run Code Online (Sandbox Code Playgroud)

感谢Ned(OP)贡献了XOR理念和上述测试用例!

  • @NedTwigg我正在考虑一个`limit(2).reduce(<some_magic>)`解决方案,但似乎在每个级别上都适合减少是错误的. (2认同)
  • @NedTwigg破解了`.reduce(Optional.empty(),(a,b) - > a.isPresent()^ b.isPresent()?b:Optional.empty())` (2认同)

S.D*_*.D. 7

如果你不介意使用Guava,你可以用你的代码包装Iterables.getOnlyElement,所以它看起来像这样:

SomeProperty distinctProperty = Iterables.getOnlyElement(
        someList.stream()
                .map(obj -> obj.getSomeProperty())
                .distinct()
                .collect(Collectors.toList()));
Run Code Online (Sandbox Code Playgroud)

IllegalArgumentException如果有多个值或没有值,则会引发,也有一个默认值的版本.


Nec*_*aux 6

为此构建收集器的更简洁方法如下:

Collectors.reducing((a, b) -> null);
Run Code Online (Sandbox Code Playgroud)

reduce collection将存储第一个值,然后在连续传递时,将当前运行值和新值传递给lambda表达式.此时,始终可以返回null,因为不会使用第一个值调用null,这将仅存储.

将其插入代码中:

Optional<SomeProperty> universalCommonProperty = someList.stream()
    .map(obj -> obj.getSomeProperty())
    .distinct()
    .collect(Collectors.reducing((a, b) -> null));
Run Code Online (Sandbox Code Playgroud)