Java 8收集器,如果存在';只有一个值

Java 8收集器,如果存在';只有一个值,java,java-8,java-stream,Java,Java 8,Java Stream,我对函数编程和流之类的东西有点生疏,但我所知道的一点非常有用 我已经多次遇到这种情况: List<SomeProperty> distinctProperties = someList.stream() .map(obj -> obj.getSomeProperty()) .distinct() .collect(Collectors.toList()); if (distinctProperties.size() == 1) { SomePr

我对函数编程和流之类的东西有点生疏,但我所知道的一点非常有用

我已经多次遇到这种情况:

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
}
当我搞砸了我的类型,或者有遗留代码时,我发现我需要这个。能够很快地说“这个集合的所有元素都共享这个属性,所以现在我可以使用这个共享属性采取一些行动”,这真是太好了。另一个例子是,当用户多重选择一些不同的元素时,您尝试查看您可以做哪些事情(如果有的话)对所有元素都有效

EDIT2:如果我的例子有误导性的话,对不起。键为单键或空键。我经常发现我在前面放了一个
不同的
,但它也可以很容易地成为另一种
过滤器

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)
可选LonSpecialItem=someList.stream()
.filter(obj->obj.isSpecial())
.collect(collector.singleOrEmpty());
[特殊]->可选。of(特殊)
[特殊,特殊]->可选。空()
[not]->可选。空()
[非,特殊]->可选。of(特殊)
[非,特殊,非]->可选。of(特殊)
EDIT3:我想我把事情搞砸了,因为我激励了单身者,而不是自己要求

Optional<Int> value = someList.stream().collect(Collectors.singleOrEmpty())
[]     -> Optional.empty()
[1]    -> Optional.of(1)
[1, 1] -> Optional.empty()
Optional value=someList.stream().collect(Collectors.singleOrEmpty())
[]->Optional.empty()
[1] ->可选。共(1)
[1,1]->可选的.empty()
如果您不介意使用,您可以使用
Iterables.getOnlyElement
包装您的代码,这样看起来就像:

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

IllegalArgumentException
如果有多个值或没有值,则会引发,还有一个值带有默认值。

您可以轻松编写自己的
收集器

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

}

这将导致创建集合的开销,但它很简单,即使您忘记首先对流进行distinct(),也可以正常工作

static<T> Collector<T,?,Optional<T>> singleOrEmpty() {
    return Collectors.collectingAndThen(
            Collectors.toSet(),
            set -> set.size() == 1 
                    ? set.stream().findAny() 
                    : Optional.empty()
    );
}
静态收集器singleOrEmpty(){
返回收集器。正在收集,然后返回(
Collectors.toSet(),
set->set.size()==1
?set.stream().findAny()
:可选。空()
);
}
仅评估前两个元素的“黑客”解决方案:

    .limit(2)
    .map(Optional::ofNullable)
    .reduce(Optional.empty(),
        (a, b) -> a.isPresent() ^ b.isPresent() ? b : Optional.empty());
一些基本解释:

单个元素[1]->映射到[可选(1)]->减少

"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)));
}
public静态可选singleOrEmpty(Stream){
返回流。限制(2)
.map(可选::of nullable)
.reduce(可选的.empty(),
(a,b)->a.isPresent()^b.isPresent()?b:可选的.empty());
}
@试验
公开无效测试(){
testCase(可选的.empty());
测试用例(可选,共(1),1);
testCase(可选的.empty(),1,1);
testCase(可选的.empty(),1,1,1);
}
私有void测试用例(可选,预期为整型…值){
Assert.assertEquals(应为singleOrEmpty(Arrays.stream(values));
}

感谢Ned(OP),他贡献了XOR思想和上面的测试用例

似乎RxJava也有类似的功能

单一( )
singleOrDefault( )

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


我宁愿选择一个
可选的
,我宁愿它是一个
收集器

另一种收集器方法:

收藏家:

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();
    }
}
公共最终类SingleCollector扩展了SingleCollectorBase{
@凌驾
公共函数完成器(){
返回a->a.getItem();
}
}
公共最终类SingleOrNullCollector扩展了SingleCollectorBase{
@凌驾
公共函数完成器(){
返回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);
    }
}
公共抽象类SingleCollectorBase实现收集器{
@凌驾
公共供应商(){
return()->newsingle();
}
@凌驾
公共双消费者累加器(){
返回(列表,项目)->list.set(项目);
}
@凌驾
公共二进制运算符组合器(){
返回(s1、s2)->{
s1.集合(s2);
返回s1;
};
}
@凌驾
公共集特征(){
返回枚举集(特征无序);
}
}
单一:

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);
    }
}
公共最终课程单{
私人物品;
私有布尔集;
公共无效集(T项){
如果(设置)抛出新的SingleException(“集合中的多个项”);
this.item=项目;
设置=真;
}
公共T getItem(){
如果(!set)抛出新的SingleException(“集合中没有项”);
退货项目;
}
公共无效集(单个其他){
如果(!other.set)返回;
成套设备(其他项目);
}
公共T getItemOrNull(){
返回集?项目:空;
}
}
公共类SingleException扩展了RuntimeException{
公共单条异常(字符串消息){
超级(信息);
}
}
测试和示例用法,尽管缺乏并行测试

public final class SingleTests {

    @Test
    public void collect_single() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");

        String collect = list.stream().collect(new SingleCollector<>());
        assertEquals("ABC", collect);
    }

    @Test(expected = SingleException.class)
    public void collect_multiple_entries() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");
        list.add("ABCD");

        list.stream().collect(new SingleCollector<>());
    }

    @Test(expected = SingleException.class)
    public void collect_no_entries() {
        ArrayList<String> list = new ArrayList<>();

        list.stream().collect(new SingleCollector<>());
    }

    @Test
    public void collect_single_or_null() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");

        String collect = list.stream().collect(new SingleOrNullCollector<>());
        assertEquals("ABC", collect);
    }

    @Test(expected = SingleException.class)
    public void collect_multiple_entries_or_null() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");
        list.add("ABCD");

        list.stream().collect(new SingleOrNullCollector<>());
    }

    @Test
    public void collect_no_entries_or_null() {
        ArrayList<String> list = new ArrayList<>();

        assertNull(list.stream().collect(new SingleOrNullCollector<>()));
    }

}
public final class SingleTests{
@试验
公共无效集合单(){
ArrayList=新建ArrayList();
列表。添加(“ABC”);
String collect=list.stream().collect(新的SingleCollector());
资产质量(“ABC”,收款);
}
@测试(预期=SingleException.class)
公共作废收集多个条目(){
ArrayList=新建ArrayList();
列表。添加(“ABC”);
列表。添加(“ABCD”);
list.stream().collect(新的SingleCollector());
}
@测试(预期=SingleException.class)
公共作废收集\u否\u条目(){
ArrayList=新建ArrayList();
list.stream().collect(新的
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)));
}
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);
    }
}
public final class SingleTests {

    @Test
    public void collect_single() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");

        String collect = list.stream().collect(new SingleCollector<>());
        assertEquals("ABC", collect);
    }

    @Test(expected = SingleException.class)
    public void collect_multiple_entries() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");
        list.add("ABCD");

        list.stream().collect(new SingleCollector<>());
    }

    @Test(expected = SingleException.class)
    public void collect_no_entries() {
        ArrayList<String> list = new ArrayList<>();

        list.stream().collect(new SingleCollector<>());
    }

    @Test
    public void collect_single_or_null() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");

        String collect = list.stream().collect(new SingleOrNullCollector<>());
        assertEquals("ABC", collect);
    }

    @Test(expected = SingleException.class)
    public void collect_multiple_entries_or_null() {
        ArrayList<String> list = new ArrayList<>();
        list.add("ABC");
        list.add("ABCD");

        list.stream().collect(new SingleOrNullCollector<>());
    }

    @Test
    public void collect_no_entries_or_null() {
        ArrayList<String> list = new ArrayList<>();

        assertNull(list.stream().collect(new SingleOrNullCollector<>()));
    }

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