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

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


当前回答

更新

@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()的收集器。

让你做一些流不支持的奇怪事情的“逃生舱口”操作是请求一个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行。

受到@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(...));

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