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

时间:2014-11-07 21:28:29

标签: 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
}

我真正想要的是:

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

我认为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()

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

EDIT2:对不起,如果我的例子有误导性。关键是 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)

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

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

8 个答案:

答案 0 :(得分:19)

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

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

答案 1 :(得分:13)

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

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

一些基本的解释:

单个元素 [1] - &gt;映射到[Optional(1)] - &gt;减少

"Empty XOR Present" yields Optional(1)

=可选(1)

两个元素 [1,2] - &gt;映射到[Optional(1),Optional(2)] - &gt;减少做:

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

= 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理念和上述测试用例!

答案 2 :(得分:7)

如果您不介意使用Guava,则可以使用Iterables.getOnlyElement包装代码,因此它看起来像这样:

SomeProperty distinctProperty = Iterables.getOnlyElement(
        someList.stream()
                .map(obj -> obj.getSomeProperty())
                .distinct()
                .collect(Collectors.toList()));
如果有多个值或没有值,则会引发

IllegalArgumentException,还有version默认值。

答案 3 :(得分:6)

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

Collectors.reducing((a, b) -> null);

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

将其插入代码:

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

答案 4 :(得分:4)

您可以轻松编写自己的Collector

public class AllOrNothing<T> implements Collector<T, Set<T>, Optional<T>>{



@Override
public Supplier<Set<T>> supplier() {
    return () -> new HashSet<>();
}



@Override
public BinaryOperator<Set<T>> combiner() {
    return (set1, set2)-> {
        set1.addAll(set2);
        return set1;
    };
}

@Override
public Function<Set<T>, Optional<T>> finisher() {
    return (set) -> {
        if(set.size() ==1){
            return Optional.of(set.iterator().next());
        }
        return Optional.empty();
    };
}

@Override
public Set<java.util.stream.Collector.Characteristics> characteristics() {
    return Collections.emptySet();
}

@Override
public BiConsumer<Set<T>, T> accumulator() {
    return Set::add;
}

}

您可以这样使用:

   Optional<T> result = myStream.collect( new AllOrNothing<>());

这是您的示例测试数据

public static void main(String[] args) {
    System.out.println(run());

    System.out.println(run(1));
    System.out.println(run(1,1));
    System.out.println(run(2,2));
    System.out.println(run(1,2));
}

private static Optional<Integer> run(Integer...ints){

    List<Integer> asList = Arrays.asList(ints);
    System.out.println(asList);
    return asList
                .stream()
                .collect(new AllOrNothing<>());
}

运行时会打印出来

[]
Optional.empty
[1]
Optional[1]
[1, 1]
Optional[1]
[2, 2]
Optional[2]

答案 5 :(得分:3)

似乎RxJava具有类似的功能in its single() operator

  

single( )singleOrDefault( )

     

如果Observable在发出单个项目后完成,则返回该项目,否则抛出异常(或返回默认项目)

我宁愿拥有Optional,而我更应该是Collector

答案 6 :(得分:1)

另一种收藏家方法:

收藏家:

var file = jQuery('#brandImageUpload').prop("files")[0];
window.URL = window.URL || window.webkitURL ;
var blobURL = window.URL.createObjectURL(file);
$('#ibrandImage').attr('src', blobURL);
$('#brandImage').slideDown();
$(this).slideUp();

SingleCollectorBase:

public final class SingleCollector<T> extends SingleCollectorBase<T> {
    @Override
    public Function<Single<T>, T> finisher() {
        return a -> a.getItem();
    }
}

public final class SingleOrNullCollector<T> extends SingleCollectorBase<T> {
    @Override
    public Function<Single<T>, T> finisher() {
        return a -> a.getItemOrNull();
    }
}

public abstract class SingleCollectorBase<T> implements Collector<T, Single<T>, T> {
    @Override
    public Supplier<Single<T>> supplier() {
        return () -> new Single<>();
    }

    @Override
    public BiConsumer<Single<T>, T> accumulator() {
        return (list, item) -> list.set(item);
    }

    @Override
    public BinaryOperator<Single<T>> combiner() {
        return (s1, s2) -> {
            s1.set(s2);
            return s1;
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return EnumSet.of(Characteristics.UNORDERED);
    }
}

测试和示例用法,尽管缺乏并行测试。

public final class Single<T> {

    private T item;
    private boolean set;

    public void set(T item) {
        if (set) throw new SingleException("More than one item in collection");
        this.item = item;
        set = true;
    }

    public T getItem() {
        if (!set) throw new SingleException("No item in collection");
        return item;
    }

    public void set(Single<T> other) {
        if (!other.set) return;
        set(other.item);
    }

    public T getItemOrNull() {
        return set ? item : null;
    }
}

public class SingleException extends RuntimeException {
    public SingleException(String message) {
        super(message);
    }
}

答案 7 :(得分:0)