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

新手上路,请多包涵

我正在尝试使用 Java 8 Stream s 在 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 (好!)

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

原文由 ryvantage 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
2 个回答

创建自定义 Collector

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

我们使用 Collectors.collectingAndThen 来构造我们想要的 Collector 通过

  1. Collectors.toList() 收集器在 List 中收集我们的对象。
  2. 最后应用一个额外的完成器,返回单个元素 - 或抛出 IllegalStateException 如果 list.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);

原文由 skiwi 发布,翻译遵循 CC BY-SA 4.0 许可协议

为了完整起见,这里是与@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));

原文由 glts 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题