Java如何按id访问过滤List<User>中嵌套的Books类属性
问题原因
你原来的代码触发索引越界的核心原因是:当filter操作没有匹配到符合条件的元素时,collect(Collectors.toList())生成的是空列表,此时调用get(0)就会触发IndexOutOfBoundsException。没有匹配到元素的场景通常包括:
- 传入的userId不存在于result列表中
- 对应用户的books列表为空
- 对应用户的books列表中没有匹配传入bookId的图书
方案1:单次查询场景,用Optional实现安全链式调用
如果只是偶尔查询一次指定userId和bookId对应的属性,直接用Stream配合Optional做判空即可,代码简洁且不会触发空指针/索引越界:
// 你可以根据业务需求选择返回默认值、抛自定义异常或者返回Optional Optional<BooksDetails> bookDetailsOpt = result.stream() // 过滤匹配userId的用户 .filter(user -> userId.equals(user.getId())) .findFirst() // 拿到对应用户的books列表 .map(User::getBooks) // 把books列表转成Stream,过滤匹配bookId的图书,取第一个 .flatMap(books -> books.stream().filter(book -> bookId.equals(book.getId())).findFirst()) // 拿到目标图书的详情属性 .map(Books::getBooksDetails); // 示例1:查询不到时返回默认值 BooksDetails bookDetails = bookDetailsOpt.orElse(new BooksDetails()); // 示例2:查询不到时抛自定义业务异常 BooksDetails bookDetails = bookDetailsOpt.orElseThrow(() -> new RuntimeException("未找到指定用户的指定图书"));
注意:Integer类型的id判断相等要用equals,不要用==,避免Integer缓存范围外的数值判断错误
方案2:多次查询场景,提前转嵌套Map提升性能
如果需要频繁根据不同的userId、bookId查询属性,提前把List转成Map<userId, Map<bookId, Books>>的嵌套结构,后续查询的时间复杂度可以降到O(1),比每次都遍历Stream高效很多:
// 提前转换为嵌套Map,注意如果存在重复userId/重复bookId的情况,需要补充第三个参数指定合并策略,避免报错 Map<Integer, Map<Integer, Books>> userBookMap = result.stream() .collect(Collectors.toMap( User::getId, user -> user.getBooks().stream() .collect(Collectors.toMap(Books::getId, Function.identity())) )); // 后续任意查询直接取值即可 BooksDetails bookDetails = Optional.ofNullable(userBookMap.get(userId)) .map(bookMap -> bookMap.get(bookId)) .map(Books::getBooksDetails) // 同样可以自定义查询不到的处理逻辑 .orElseThrow(() -> new RuntimeException("未找到指定资源"));
内容的提问来源于stack exchange,提问作者NoahH
相关产品推荐
相关产品推荐

