我试图使用Java 8流在LinkedList中查找元素。但是,我想保证与筛选条件有且只有一个匹配。

以这段代码为例:

public static void main(String[] args) {

    LinkedList<User> users = new LinkedList<>();
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
    System.out.println(match.toString());
}

static class User {

    @Override
    public String toString() {
        return id + " - " + username;
    }

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

这段代码根据用户的ID查找用户。但是不能保证有多少用户匹配过滤器。

更改过滤器行为:

User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();

将抛出一个NoSuchElementException(很好!)

但是,如果有多个匹配,我希望它抛出一个错误。有办法做到这一点吗?


创建一个自定义收集器

public static <T> Collector<T, ?, T> toSingleton() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                if (list.size() != 1) {
                    throw new IllegalStateException();
                }
                return list.get(0);
            }
    );
}

我们使用收集工具。然后构造我们想要的收集器

使用collector. tolist()收集器在列表中收集对象。 在最后应用一个额外的结束符,返回单个元素—或者抛出一个IllegalStateException if列表。Size != 1。

用作:

User resultUser = users.stream()
        .filter(user -> user.getId() > 0)
        .collect(toSingleton());

然后,您可以随心所欲地定制这个Collector,例如在构造函数中将异常作为参数,将其调整为允许两个值,甚至更多。

另一种可能不那么优雅的解决方案是:

您可以使用包含peek()和AtomicInteger的“变通方法”,但实际上不应该使用它。

你可以做的只是把它收集到一个List中,就像这样:

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
List<User> resultUserList = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.toList());
if (resultUserList.size() != 1) {
    throw new IllegalStateException();
}
User resultUser = resultUserList.get(0);

更新

@Holger的评论建议不错:

Optional<User> match = users.stream()
              .filter((user) -> user.getId() > 1)
              .reduce((u, v) -> { throw new IllegalStateException("More than one ID found") });

原来的答案

异常由Optional#get抛出,但如果有多个元素,则没有帮助。你可以在一个只接受一个项的集合中收集用户,例如:

User match = users.stream().filter((user) -> user.getId() > 1)
                  .collect(toCollection(() -> new ArrayBlockingQueue<User>(1)))
                  .poll();

它会抛出一个java.lang.IllegalStateException:队列已满,但感觉太粗糙了。

或者你可以使用减法和可选的结合:

User match = Optional.ofNullable(users.stream().filter((user) -> user.getId() > 1)
                .reduce(null, (u, v) -> {
                    if (u != null && v != null)
                        throw new IllegalStateException("More than one ID found");
                    else return u == null ? v : u;
                })).get();

约简的结果是:

如果没有找到用户,则为Null 如果只找到一个,则返回用户 如果发现多个异常,则抛出异常

然后将结果包装在可选的。

但最简单的解决方案可能是收集到一个集合,检查它的大小为1,并获得唯一的元素。


Guava提供了MoreCollectors.onlyElement(),它在这里做正确的事情。但如果你必须自己做,你可以为这个创建自己的Collector:

<E> Collector<E, ?, Optional<E>> getOnly() {
  return Collector.of(
    AtomicReference::new,
    (ref, e) -> {
      if (!ref.compareAndSet(null, e)) {
         throw new IllegalArgumentException("Multiple values");
      }
    },
    (ref1, ref2) -> {
      if (ref1.get() == null) {
        return ref2;
      } else if (ref2.get() != null) {
        throw new IllegalArgumentException("Multiple values");
      } else {
        return ref1;
      }
    },
    ref -> Optional.ofNullable(ref.get()),
    Collector.Characteristics.UNORDERED);
}

…或者使用你自己的Holder类型而不是AtomicReference。您可以尽可能多地重用收集器。


你试过这个吗

long c = users.stream().filter((user) -> user.getId() == 1).count();
if(c > 1){
    throw new IllegalStateException();
}

long count()
Returns the count of elements in this stream. This is a special case of a reduction and is equivalent to:

     return mapToLong(e -> 1L).sum();

This is a terminal operation.

来源:https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html


其他涉及编写自定义Collector的答案可能更有效(如Louis Wasserman的+1),但如果你想要简洁,我建议如下:

List<User> result = users.stream()
    .filter(user -> user.getId() == 1)
    .limit(2)
    .collect(Collectors.toList());

然后验证结果列表的大小。

if (result.size() != 1) {
  throw new IllegalStateException("Expected exactly one user but got " + result);
User user = result.get(0);
}

让你做一些流不支持的奇怪事情的“逃生舱口”操作是请求一个Iterator:

Iterator<T> it = users.stream().filter((user) -> user.getId() < 0).iterator();
if (!it.hasNext()) {
    throw new NoSuchElementException();
} else {
    result = it.next();
    if (it.hasNext()) {
        throw new TooManyElementsException();
    }
}

Guava有一个方便的方法来获取一个Iterator并获取唯一的元素,如果有零个或多个元素就抛出,这可以替换这里底部的n-1行。


另一种选择是使用reduction: (本例使用字符串,但可以轻松应用于包括User在内的任何对象类型)

List<String> list = ImmutableList.of("one", "two", "three", "four", "five", "two");
String match = list.stream().filter("two"::equals).reduce(thereCanBeOnlyOne()).get();
//throws NoSuchElementException if there are no matching elements - "zero"
//throws RuntimeException if duplicates are found - "two"
//otherwise returns the match - "one"
...

//Reduction operator that throws RuntimeException if there are duplicates
private static <T> BinaryOperator<T> thereCanBeOnlyOne()
{
    return (a, b) -> {throw new RuntimeException("Duplicate elements found: " + a + " and " + b);};
}

所以对于User的情况,你会有:

User match = users.stream().filter((user) -> user.getId() < 0).reduce(thereCanBeOnlyOne()).get();

为了完整起见,下面是@prunge的精彩回答对应的“一行”:

User user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })
        .get();

这将从流中获得唯一匹配的元素,即抛出

NoSuchElementException,如果流是空的,或者 IllegalStateException,如果流包含多个匹配元素。

这种方法的一种变体可以避免过早抛出异常,而是将结果表示为一个包含唯一元素的Optional,如果有零个或多个元素,则什么都不包含(空):

Optional<User> user1 = users.stream()
        .filter(user -> user.getId() == 1)
        .collect(Collectors.reducing((a, b) -> null));

我们可以使用RxJava(非常强大的响应式扩展库)

LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

User userFound =  Observable.from(users)
                  .filter((user) -> user.getId() == 1)
                  .single().toBlocking().first();

如果没有找到用户或找到多个用户,单个操作符将抛出异常。


如果你不介意使用第三方库,来自cyclops-streams的SequenceM(和来自simple-react的LazyFutureStream)都有single和singleOptional操作符。

如果流中有0个或多个元素,singleOptional()将抛出异常,否则将返回单个值。

String result = SequenceM.of("x")
                          .single();

SequenceM.of().single(); // NoSuchElementException

SequenceM.of(1, 2, 3).single(); // NoSuchElementException

String result = LazyFutureStream.fromStream(Stream.of("x"))
                          .single();

如果流中没有值或有多个值,singleOptional()返回Optional.empty()。

Optional<String> result = SequenceM.fromStream(Stream.of("x"))
                          .singleOptional(); 
//Optional["x"]

Optional<String> result = SequenceM.of().singleOptional(); 
// Optional.empty

Optional<String> result =  SequenceM.of(1, 2, 3).singleOptional(); 
// Optional.empty

披露-我是这两个库的作者。


收藏家。toMap(keyMapper, valueMapper)使用抛出合并来处理具有相同键的多个条目,这很简单:

List<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));

int id = 1;
User match = Optional.ofNullable(users.stream()
  .filter(user -> user.getId() == id)
  .collect(Collectors.toMap(User::getId, Function.identity()))
  .get(id)).get();

对于重复的键,您将得到一个IllegalStateException。但在最后,我不确定如果使用if,代码是否会更可读。


使用Guava的morecolltors . onlyelement()(源代码)。

如果流包含两个或多个元素,则抛出IllegalArgumentException异常;如果流为空,则抛出NoSuchElementException异常。

用法:

import static com.google.common.collect.MoreCollectors.onlyElement;

User match =
    users.stream().filter((user) -> user.getId() < 0).collect(onlyElement());

Guava有一个名为MoreCollectors.onlyElement()的收集器。


我正在使用这两个收集器:

public static <T> Collector<T, ?, Optional<T>> zeroOrOne() {
    return Collectors.reducing((a, b) -> {
        throw new IllegalStateException("More than one value was returned");
    });
}

public static <T> Collector<T, ?, T> onlyOne() {
    return Collectors.collectingAndThen(zeroOrOne(), Optional::get);
}

使用收集器:

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

用法:

Optional<User> result = users.stream()
        .filter((user) -> user.getId() < 0)
        .collect(singleElementCollector());

我们返回一个Optional,因为我们通常不能假设集合只包含一个元素。如果你已经知道这是什么情况,请致电:

User user = result.orElseThrow();

这就把处理错误的负担放在了调用者身上——这是应该的。


使用减少

这是我发现的更简单灵活的方法(基于@prunge的答案)

Optional<User> user = users.stream()
        .filter(user -> user.getId() == 1)
        .reduce((a, b) -> {
            throw new IllegalStateException("Multiple elements: " + a + ", " + b);
        })

这样你就可以得到:

Optional -和你的对象一样,如果不存在则使用Optional.empty() 如果有多个元素,则使用Exception(最终使用YOUR自定义类型/消息)


我认为这种方式更简单:

User resultUser = users.stream()
    .filter(user -> user.getId() > 0)
    .findFirst().get();

User match = users.stream().filter((user) -> user.getId()== 1).findAny().orElseThrow(()-> new IllegalArgumentException());

受到@skiwi的启发,我用下面的方法解决了这个问题:

public static <T> T toSingleton(Stream<T> stream) {
    List<T> list = stream.limit(1).collect(Collectors.toList());
    if (list.isEmpty()) {
        return null;
    } else {
        return list.get(0);
    }
}

然后:

User user = toSingleton(users.stream().filter(...).map(...));

如果你不使用Guava或Kotlin,这里有一个基于@skiwi和@Neuron答案的解决方案。

users.stream().collect(single(user -> user.getId() == 1));

or

users.stream().collect(optional(user -> user.getId() == 1));

其中single和optional是返回相应收集器的静态导入函数。

我认为,如果将过滤逻辑移到收集器内部,看起来会更简洁。同样,如果您碰巧用.filter删除字符串,代码中也不会中断任何内容。

代码https://gist.github.com/overpas/ccc39b75f17a1c65682c071045c1a079的要点


 List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
Integer value  = list.stream().filter((x->x.intValue()==8)).findFirst().orElse(null);

我已经使用整数类型而不是原语,因为它将有空指针异常。你只需要处理这个异常…看起来很简洁,我觉得;)


我自己尝试了一个示例代码,这里是解决方案。

User user = Stream.of(new User(2), new User(2), new User(1), new User(2))
            .filter(u -> u.getAge() == 2).findFirst().get();

和用户类

class User {
    private int age;

public User(int age) {
    this.age = age;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
 }
}

使用Reduce和Optional

来自Fabio Bonfante的回应:

public <T> T getOneExample(Collection<T> collection) {
    return collection.stream()
        .filter(x -> /* do some filter */)
        .reduce((x,y)-> {throw new IllegalStateException("multiple");})
        .orElseThrow(() -> new NoSuchElementException("none"));
}

public List<state> getAllActiveState() {
    List<Master> master = masterRepository.getActiveExamMasters();
    Master activeMaster = new Master();
    try {
        activeMaster = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {
            throw new IllegalStateException();
        }).get();
        return stateRepository.getAllStateActiveId(activeMaster.getId());
    } catch (IllegalStateException e) {
        logger.info(":More than one status found TRUE in Master");
        return null;
    }
}

In this above code, As per the condition if its find more than one true in the list then it will through the exception. When it through the error will showing custom message because it easy maintain the logs on server side. From Nth number of element present in list just want only one element have true condition if in list there are more than one elements having true status at that moment it will through an exception. after getting all the this we using get(); to taking that one element from list and stored it into another object. If you want you added optional like Optional<activeMaster > = master.stream().filter(status -> status.getStatus() == true).reduce((u, v) -> {throw new IllegalStateException();}).get();