引言
在 Java 开发中,JDK 提供的集合框架(如 List、Set、Map)已经能够满足大部分日常需求。然而,当面对更复杂的数据关系时,开发者往往需要手动组合这些基础集合(例如 Map),这不仅代码冗长,还容易引发空指针异常或逻辑错误。
Google Guava 库引入了一系列强大的新集合类型,旨在填补 JDK 的空白。这些集合类型设计精良,与 JDK 集合框架完美共存,能够以更简洁、更安全的方式处理多维数据映射、计数、双向查找及范围查询等场景。本文将详细介绍 Guava 中的核心新集合类型:Multiset、Multimap、BiMap、Table、ClassToInstanceMap、RangeSet 和 RangeMap。
1. Multiset:高效的元素计数器
痛点
在传统 Java 中,统计元素出现次数通常需要使用 Map,代码繁琐且易错:- Map<String, Integer> counts = new HashMap<>();
- for (String word : words) {
- Integer count = counts.get(word);
- if (count == null) {
- counts.put(word, 1);
- } else {
- counts.put(word, count + 1);
- }
- }
复制代码 解决方案
Guava 的 Multiset 允许元素重复出现,它结合了 List(无序集合)和 Map(元素到计数的映射)的特性。
核心特性:
- 作为 Collection:add(E) 增加一个实例,size() 返回所有实例的总数,迭代器遍历每个实例。
- 作为 Map 视图:count(E) 返回元素出现次数,elementSet() 返回去重后的元素集合。
- 内存优化:内存消耗仅与不同元素的数量成线性关系,而非总实例数。
常用方法:
方法描述count(E)返回元素出现的次数add(E, int)增加指定数量的该元素实例setCount(E, int)直接设置元素的计数值elementSet()获取所有不同元素的集合视图entrySet()获取 {元素, 计数} 的条目集合视图实现类推荐:
- HashMultiset:对应 HashMap,支持 null,查询 O(1)。
- TreeMultiset:对应 TreeMap,支持排序。
- ConcurrentHashMultiset:线程安全版本。
注意:Multiset 不是 Map。它不包含计数为 0 的元素,且 size() 返回的是总实例数而非不同元素个数。
2. Multimap:一对多映射的优雅解法
痛点
处理“一个键对应多个值”的场景(如图论中的邻接表)时,开发者常使用 Map。这导致每次 put/get 都需要判断列表是否存在,代码臃肿。
解决方案
Multimap 将键映射到值的集合,简化了操作逻辑。它可以被视为一组键值对映射,也可以视为键到集合的映射。
核心特性:
- 自动初始化:get(key) 永远返回一个非 null 的集合视图(即使该键尚不存在)。对该集合的修改会直接写回 Multimap。
- 灵活视图:
- asMap():将其视为 Map。
- keys():将键视为 Multiset,反映每个键关联的值数量。
- values():将所有值扁平化为一个大的 Collection。
构建方式:
推荐使用 MultimapBuilder 进行类型安全的构建:- // 创建一个 Key 为树结构,Value 为 ArrayList 的 Multimap
- ListMultimap<String, Integer> treeListMultimap =
- MultimapBuilder.treeKeys().arrayListValues().build();
复制代码 常用操作:
- put(K, V):添加单个映射。
- get(K):获取值集合视图。
- removeAll(K):移除某键关联的所有值。
- replaceValues(K, Iterable):替换某键关联的所有值。
实现类推荐:
- ArrayListMultimap / HashMultimap:最常用,分别对应 List 和 Set 语义。
- LinkedHashMultimap:保持插入顺序。
- ImmutableListMultimap:不可变版本,线程安全。
3. BiMap:双向唯一映射
痛点
需要反向查找(通过 Value 找 Key)时,通常维护两个 Map 并手动同步,极易出错。
解决方案
BiMap(双向 Map)强制 Key 和 Value 都是唯一的,并提供 inverse() 方法获取反向视图。
核心特性:
- 值唯一性:put(key, value) 时,如果 value 已存在,会抛出 IllegalArgumentException。
- 强制覆盖:若需覆盖已存在的 value 映射,使用 forcePut(key, value)。
- 反向视图:biMap.inverse().get(value) 即可获取对应的 key,无需额外维护反向 Map。
实现类:
- HashBiMap:基于哈希表。
- EnumBiMap / EnumHashBiMap:针对枚举类型优化。
- ImmutableBiMap:不可变版本。
4. Table:二维映射结构
痛点
处理类似矩阵或表格的数据(如 Map)时,嵌套 Map 的访问和遍历非常不便。
解决方案
Table 提供了原生的二维映射支持,通过行(Row)和列(Column)两个键来定位值。
核心特性:
- 多维度视图:
- row(R):返回该行所有列数据的 Map 视图。
- column(C):返回该列所有行数据的 Map 视图。
- cellSet():返回所有单元格 {Row, Column, Value} 的集合。
- 便捷访问:直接通过 table.get(rowKey, columnKey) 获取值。
实现类:
- HashBasedTable:底层由 HashMap 支持,最通用。
- TreeBasedTable:支持行列排序。
- ArrayTable:当行列空间固定且密集时,性能极高(基于二维数组)。
5. ClassToInstanceMap:类型安全的类到实例映射
痛点
当 Map 的 Key 是 Class 对象,Value 是该 Class 的实例时,传统 Map 在获取时需要强制类型转换,既不安全也不优雅。
解决方案
ClassToInstanceMap<B> 扩展了 Map 接口,提供了泛型安全的方法:
- T getInstance(Class):直接返回正确类型的实例,无需强转。
- T putInstance(Class, T):存入实例。
示例:- ClassToInstanceMap<Number> numberDefaults = MutableClassToInstanceMap.create();
- numberDefaults.putInstance(Integer.class, Integer.valueOf(0));
- // 获取时自动推断为 Integer,无需 (Integer) 强转
- Integer i = numberDefaults.getInstance(Integer.class);
复制代码 6. RangeSet 与 RangeMap:范围数据处理
RangeSet:不连续范围的集合
用于管理一组不相交的区间。添加区间时,相邻或重叠的区间会自动合并。
特性:
- 自动合并:添加 [1, 10] 和 [11, 20](若离散域连续)可能合并为大区间。
- 丰富查询:
- contains(value):判断值是否在任意区间内。
- rangeContaining(value):返回包含该值的具体区间。
- complement():获取补集。
- subRangeSet(range):获取交集视图。
RangeMap:范围到值的映射
将不相交的区间映射到具体的值。与 RangeSet 不同,RangeMap 不会自动合并相邻且值相同的区间(除非显式操作),保持映射的精确性。
特性:
- 区间切割:放入新区间时,若与现有区间重叠,会自动切割现有区间以保证不相交。
- 视图:asMapOfRanges() 可将其视为 Map 进行遍历。
注意:RangeSet 和 RangeMap 依赖 JDK 1.6+ 的 NavigableMap 特性。
总结
Guava 的新集合类型不仅仅是 API 的扩充,更是编程思维的升级。它们将常见的复杂数据结构模式(计数、一对多、双向映射、二维表、范围查询)封装为独立、高效且类型安全的抽象。
- 需要计数?用 Multiset 代替 Map。
- 需要一对多?用 Multimap 告别嵌套 Map 的判断逻辑。
- 需要反向查找?用 BiMap 确保数据一致性。
- 需要二维索引?用 Table 简化矩阵操作。
- 需要范围管理?用 RangeSet/RangeMap 处理区间逻辑。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |