我试图使用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(很好!)

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


当前回答

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

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

用法:

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

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

其他回答

创建一个自定义收集器

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

使用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"));
}

更新

@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,并获得唯一的元素。

我们可以使用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();

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

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