将Java Stream过滤为1个且仅有1个元素

332

我正在尝试使用Java 8的Stream来查找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。但是不能保证有多少个User与筛选条件匹配。

将筛选条件的行改为:

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

会抛出一个NoSuchElementException(很好!)

不过如果有多个匹配项,我希望它抛出一个错误。有没有办法做到这一点?


count() 是一个终端操作,因此您不能这样做。在此之后无法再使用该流。 - Alexis C.
好的,谢谢@ZouZou。我不是完全确定那个方法做了什么。为什么没有Stream::size - ryvantage
10
因为流(stream)只能使用一次:计算它的大小意味着对它进行“迭代”,在此之后,您将无法再使用该流。 - assylias
4
哇,那条评论帮助我更好地理解了“流”(Stream)…… - ryvantage
4
当你意识到需要使用LinkedHashSet(假设你需要保留插入顺序)或者HashSet时,你会明白这一点。如果你的集合只用于查找单个用户ID,那么为什么要收集所有其他项?如果可能总是需要查找某个唯一的用户ID,为什么要使用列表而不是集合?你在逆向编程。使用正确的集合来完成任务,避免这种头痛。 - smac89
显示剩余2条评论
24个回答

0

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

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

或者

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

其中singleoptional是静态导入函数,返回相应的收集器。

我认为如果过滤逻辑被移动到收集器内部,代码会更加简洁。而且,如果你不小心删除了带有.filter的字符串,代码也不会出错。

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


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

6
虽然这段代码可能解决了问题,但是包括一个解释,说明它如何解决问题以及为什么能够解决问题,会有助于提高您的帖子质量,并可能导致更多的赞。请记住,您正在回答未来读者的问题,而不仅仅是现在的提问者。请编辑您的答案添加解释,并指出适用的限制和假设。 - David Buck

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

1
此解决方案无法检测流中存在多个值的情况。因此,它会被忽略。 - David Nouls
实际上,我只想获取流中的第一个元素。 - JavAlex
1
原始问题要求只有一个答案。而被接受的答案则抛出了一个异常。 - David Nouls
1
是的...如果你想要做完全相同的事情,你可以只需执行 stream.findFirst().orElse(null),这与你在这里所做的完全等效,并且更易读。 - LordOfThePigs

-3

你试过这个吗?

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


3
据说count()不适合使用,因为它是一个终端操作。 - ryvantage
如果这确实是一句引语,请添加您的来源。 - Neuron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接