
在java开发中,我们经常需要根据一组数据构建映射(map),以便快速查找和关联信息。一个常见场景是,我们有一个productkey列表,需要为每个productkey查找并关联一个productdetail。然而,并非所有的productkey都能找到对应的productdetail,这意味着在最终的映射中,某些productkey可能对应的值是null。此外,productkey本身也可能需要通过某种逻辑(如findproductkey方法)来确认其有效性或获取正确的实例。
我们的目标是:
为了实现这一目标,我们将利用Java 8的Stream API,特别是Collectors.toMap()方法。
构建目标映射的关键在于合理设计Stream管道,以处理ProductKey的查找和潜在的空值。
假设我们有以下基本类定义(为简洁,省略了部分Lombok注解和getter/setter):
立即学习“Java免费学习笔记(深入)”;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
static class ProductKey {
@EqualsAndHashCode.Include
private Long productCode;
@EqualsAndHashCode.Include
private Long productDetailCode; // 假设此字段可能为null,或不用于查找
}
@Data
static class Product {
@EqualsAndHashCode.Include
private ProductKey productKey;
private ProductDetail productDetail; // 初始可能为null
}
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
static class ProductDetail {
@EqualsAndHashCode.Include
private Long productCode;
private String description;
private BigDecimal price;
private String category;
}
// 辅助方法,模拟查找ProductKey,可能返回Optional.empty()
public static Optional<ProductKey> findProductKey(Long productCode, List<ProductKey> productKeys) {
return productKeys.stream()
.filter(productKey -> productCode.equals(productKey.getProductCode()))
// takeWhile(productKey -> productKey != null) 在这里是冗余的,
// 因为filter已经确保了productKey非null,且Optional.findFirst()会处理空流
.findFirst();
}
// 辅助方法,模拟将ProductDetail列表转换为Map<Long, ProductDetail>
public static Map<Long, ProductDetail> mapProductCodeToProductDetail(List<ProductDetail> productDetailList) {
return productDetailList.stream()
.collect(Collectors.toMap(
ProductDetail::getProductCode,
Function.identity(),
(existing, replacement) -> existing // 处理重复productCode的情况
));
}现在,我们来构建Map<ProductKey, ProductDetail>:
// 模拟数据初始化
List<ProductKey> productKeyList = List.of(
new ProductKey() {{ setProductCode(101L); setProductDetailCode(1L); }},
new ProductKey() {{ setProductCode(102L); setProductDetailCode(2L); }},
new ProductKey() {{ setProductCode(103L); setProductDetailCode(3L); }}, // 假设103没有对应的ProductDetail
new ProductKey() {{ setProductCode(101L); setProductDetailCode(4L); }} // 模拟重复的productCode,但ProductKey不同
);
List<ProductDetail> productDetailRawList = List.of(
new ProductDetail() {{ setProductCode(101L); setDescription("Detail A"); setPrice(BigDecimal.valueOf(10.0)); }},
new ProductDetail() {{ setProductCode(102L); setDescription("Detail B"); setPrice(BigDecimal.valueOf(20.0)); }}
);
// 首先,将原始的ProductDetail列表转换为以productCode为键的Map,方便查找
Map<Long, ProductDetail> productDetailMap = mapProductCodeToProductDetail(productDetailRawList);
// 构建 Map<ProductKey, ProductDetail>
Map<ProductKey, ProductDetail> prodDetailByKey = productKeyList.stream()
// 1. 调用 findProductKey 模拟查找,将每个 ProductKey 转换为 Optional<ProductKey>
.map(productKey -> findProductKey(productKey.getProductCode(), productKeyList))
// 2. 过滤掉空的 Optional,只保留成功找到 ProductKey 的元素
.filter(Optional::isPresent)
// 3. 从 Optional 中提取实际的 ProductKey 对象
.map(Optional::get)
// 4. 使用 Collectors.toMap() 进行收集
.collect(Collectors.toMap(
Function.identity(), // keyMapper: ProductKey 本身就是我们想要的键
productKey -> productDetailMap.get(productKey.getProductCode()), // valueMapper: 根据 ProductKey 的 productCode 从 productDetailMap 中获取 ProductDetail
(existing, replacement) -> existing // mergeFunction: 处理如果 ProductKey 列表有重复 ProductKey 导致键冲突的情况,这里选择保留旧值
));
System.out.println("生成的映射 (prodDetailByKey):");
prodDetailByKey.forEach((key, value) -> System.out.println(" Key: " + key.getProductCode() + ", Value: " + (value != null ? value.getDescription() : "null")));代码解析:
一旦我们有了Map<ProductKey, ProductDetail>,更新Product列表就变得非常直接。对于这种带有副作用的操作(修改现有对象),使用Iterable.forEach()通常比Stream的map().collect()更清晰和高效。
// 模拟 Product 列表
List<Product> products = List.of(
new Product() {{ setProductKey(new ProductKey() {{ setProductCode(101L); setProductDetailCode(1L); }}); }},
new Product() {{ setProductKey(new ProductKey() {{ setProductCode(102L); setProductDetailCode(2L); }}); }},
new Product() {{ setProductKey(new ProductKey() {{ setProductCode(103L); setProductDetailCode(3L); }}); }} // 103没有对应的ProductDetail
);
System.out.println("\n更新前的 Product 列表:");
products.forEach(p -> System.out.println(" ProductKey: " + p.getProductKey().getProductCode() + ", Detail: " + (p.getProductDetail() != null ? p.getProductDetail().getDescription() : "null")));
// 使用 forEach 循环更新 Product 对象的 ProductDetail
products.forEach(product -> {
ProductDetail detail = prodDetailByKey.get(product.getProductKey());
product.setProductDetail(detail);
});
System.out.println("\n更新后的 Product 列表:");
products.forEach(p -> System.out.println(" ProductKey: " + p.getProductKey().getProductCode() + ", Detail: " + (p.getProductDetail() != null ? p.getProductDetail().getDescription() : "null")));代码解析:
Map.get()返回null的含义: 当我们从一个Map中通过get(key)方法获取值时,如果Map中不包含该key,或者该key对应的值就是null,get()方法都会返回null。在我们的场景中,prodDetailByKey.get(product.getProductKey())会返回null,如果productDetailMap中没有product.getProductKey().getProductCode()对应的详情。这意味着Product对象的productDetail属性将被设置为null,这正是我们处理“Product没有ProductDetail”情况的方式。
移除现有映射中的空值: 如果在某些场景下,你希望从一个已经存在的映射中移除所有值为null的条目,可以使用以下方法:
import java.util.Objects; // ... // 假设 productDetailMap 包含一些值为 null 的条目 productDetailMap.values().removeIf(Objects::isNull); // 或者如果你想移除键值对,可以迭代 entrySet productDetailMap.entrySet().removeIf(entry -> entry.getValue() == null);
但请注意,这通常是在Map构建完成后进行的清理操作,而不是构建过程中处理潜在null值的方式。在上述教程的构建过程中,Collectors.toMap允许将null作为值存储。
ProductKey的equals()和hashCode(): 作为Map的键,ProductKey类必须正确实现equals()和hashCode()方法。Lombok的@EqualsAndHashCode注解(并指定onlyExplicitlyIncluded = true)是一个方便的解决方案,它确保只有被@EqualsAndHashCode.Include标记的字段才参与到这两个方法的计算中,这对于Map的正确行为至关重要。
Optional的正确使用: Optional是为了避免直接返回null并强制调用者处理“值可能不存在”的情况。在Stream管道中,它常用于表示中间结果的存在性,并通过filter(Optional::isPresent)和map(Optional::get)来安全地提取值。
通过Java 8的Stream API和Collectors.toMap(),我们可以优雅且高效地构建复杂的映射,即使键或值可能涉及Optional或null。关键在于理解Stream管道的各个操作符(map、filter)如何协同工作,以及Collectors.toMap()的keyMapper、valueMapper和mergeFunction参数如何定义映射的构建逻辑。在更新对象属性时,对于带有副作用的操作,Iterable.forEach()往往能提供更简洁直观的代码。正确处理equals()和hashCode()以及理解Optional的语义,是确保这些操作正确性的基础。
以上就是Java 8 Stream API:高效构建含空值键的映射并更新关联对象的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号