本文基于本仓库(Google Guava,
com.google.common.*)的源码结构整理,面向已经熟悉 JDK 集合(List/Set/Map/Queue)的 Java 开发者,说明 Guava 提供的集合类究竟解决了哪些 JDK 没有覆盖、或覆盖得很勉强的问题。
1. 引言
很多刚接触 Guava 的同事都会有一个疑问:JDK 已经提供了完整的集合框架,日常开发也足够用,为什么还要引入一个第三方库来"再提供一遍集合"?
这个问题背后有一种常见的误解,认为 Guava 是在"重新实现 ArrayList 和 HashMap"。实际上,Guava 的集合模块几乎不与 JDK 的集合类型竞争,而是在 JDK 之上补齐它缺失的类型 与更好用的工具。理解这一点,是正确使用 Guava 的前提。
2. 背景:JDK 集合与 Guava 的定位
JDK 集合框架的设计哲学是"最小核心 API"------它只提供最通用的数据结构与少数抽象操作(Collections 工具类的方法也相当有限)。这是有意为之:JDK 需要长期稳定,不会为每一种业务组合都专门造一个类型。
Guava 源自 Google 内部的 "Google Collections" 库(约 2007 年),其设计目标恰恰相反:以开发效率与表达力 为优先,把那些"标准但样板极多"的集合用法沉淀成现成的类型与工具。它在时间上也早于 JDK 后续的多项改进------Java 8 的 lambda / Stream / Optional、Java 8 的无符号 API、以及更现代的 ConcurrentHashMap 能力,Guava 都提前多年提供了对应方案,并且至今在功能丰富度上仍超过 JDK 的等价物。
因此,二者是互补关系:JDK 提供地基,Guava 在地基上补出缺失的类型与顺手的工具。
3. 核心观点:扩展,而非替代
Guava 的集合价值可以概括为两条:
- 提供 JDK 根本没有的集合类型 :如
Multimap、Multiset、BiMap、Table、Range系列、Graph系列,以及缓存、BloomFilter等。 - 提供 JDK 有但不好用的东西的更好版本 :如真正不可变的集合、声明式的集合工具类、带回收策略的本地缓存、可组合的
Future等。
换句话说,JDK 给了"标准积木",Guava 补的是"那几种 JDK 没造出来的积木 + 一堆省样板的工具"。
4. 缺失的类型:Guava 补齐了什么
下面按"JDK 缺什么 → Guava 给什么 → 解决什么问题"逐类说明。
4.1 不可变集合(Immutable 系列)
JDK 提供了 Collections.unmodifiableList() 等方法,但它们只是视图包装:底层原始集合一旦修改,包装后的"不可变"视图也会随之变化。要获得真正不可变的对象,开发者必须手动拷贝再包装,代码啰嗦且容易因遗漏而埋下隐患;在需要防御性拷贝、跨线程共享的场景里,这类样板更是无处不在。
Guava 的 ImmutableList / ImmutableSet / ImmutableMap,以及 ImmutableMultimap / ImmutableTable / ImmutableBiMap 等,通过构建器(ImmutableList.of(...)、builder().add()...build())产出真正不可变的对象:线程安全、可安全共享,并且在相等比较上做了优化。它把"不可变"从编码约定变成了类型层面的保证。
4.2 Multimap(ListMultimap / SetMultimap)
JDK 中没有"一个键对应多个值"的一等公民类型。常见写法是 Map<K, List<V>>,但每次 put 都要判断键是否存在、初始化列表、contains 检查,删除时还要清理空集合------样板代码极多,且容易出错。
Multimap 直接表达"键到值集合"的语义:put(k, v) 自动管理内部容器,get(k) 永远不会返回 null,还能拿到 asMap() 等视图,以及 ListMultimap / SetMultimap 等约束值容器类型的子接口。
4.3 Multiset(HashMultiset / TreeMultiset / ConcurrentHashMultiset)
统计元素出现次数是常见需求。JDK 里通常只能借助 Map<T, Integer>,每次 get / put + 1 都要处理 null 与自增逻辑,样板多且易错。
Multiset 是"带计数的集合":count() 查询出现次数、add(k, 3) 批量增加、elementSet() 拿到不重复元素视图。它既有 Set 的简洁,又承担了计数的职责。
4.4 BiMap(HashBiMap / EnumBiMap / ImmutableBiMap)
当需要"从值反查键"时,JDK 没有内建支持,只能再维护一个反向 Map,并且要手动保证值唯一、两侧同步------这是典型的易错点。
BiMap 强制值唯一,并通过 inverse() 一键得到反向映射,省去了维护两份结构及其一致性的负担。
4.5 Table(ArrayTable / HashBasedTable / TreeBasedTable / RowSortedTable)
二维数据(行、列、值)在 JDK 中只能表达为 Map<R, Map<C, V>>,嵌套结构既不直观,行列遍历也很繁琐。
Table 用 (row, col) 双键访问数据,提供 rowMap()、columnMap()、cellSet() 等视图,天然对应"表格"这一常见数据模型。
4.6 Range / RangeSet / RangeMap(ContiguousSet / DiscreteDomain)
JDK 中没有"区间"或"连续范围"的抽象。要表达"可接受的年龄区间 18--65""营业时段""阶梯定价区间"等,只能靠手写比较逻辑和集合模拟,既容易出错也难以复用。
Range 表示单个区间,RangeSet 表示不相交区间的并集(适合"多个时段"),RangeMap 表示区间到值的映射(适合"区间定价")。它们内置了合并、查询、补集等运算。
4.7 集合工具类
JDK 的 Collections 工具类方法很少,且缺乏声明式能力:构造比较器很啰嗦;集合间的差集、交集、过滤、转换没有现成 API;像"固定大小的优先队列""只保留最近 N 个元素的队列""字符串 intern 池"这类特殊集合也需要自己实现。
Guava 提供了 Lists / Sets / Maps / Multimaps / Multisets / Queues / Tables 等工厂与操作类,以及 Iterables / Iterators / Collections2 / Streams / MoreCollectors;排序方面则有 Ordering / ComparisonChain / Comparators;还有 FluentIterable、PeekingIterator、MinMaxPriorityQueue、EvictingQueue、Interner / Interners 等专用类型。
5. 汇总对照
| JDK 现状(痛点) | Guava 对应方案 |
|---|---|
Map<K, List<V>> 维护繁琐(判空 / 初始化 / 去重) |
Multimap / ListMultimap / SetMultimap |
计数靠 Map<T, Integer>,get/put +1 样板易错 |
Multiset(Hash / Tree / Concurrent 实现) |
| 双向查找需维护两个 Map,值唯一难保证 | BiMap + inverse() 反向视图 |
Map<R, Map<C, V>> 二维嵌套难看 |
Table(Array / Hash / TreeBased 实现) |
| 无区间 / 连续范围抽象,只能手写模拟 | Range / RangeSet / RangeMap |
unmodifiableX 仅包装,底层仍可变 |
ImmutableList / ImmutableSet / ImmutableMap ... 真不可变 |
Collections 工具薄,特殊集合需自写 |
Lists / Sets / Maps / Ordering / Iterables 等 |
6. 不止集合:Guava 补齐的基础设施
Guava 的"集合之外"同样填补了 JDK 长期缺失或薄弱的能力,值得在选型时一并考虑:
base:Joiner/Splitter(字符串连接与分割,JDK 的String.join出现晚且能力弱)、Preconditions(checkNotNull等一行校验)、Optional(早于 Java 8)、CharMatcher(字符分类处理)、CaseFormat(命名风格转换)、Stopwatch(计时)、Throwables。cache:Cache/LoadingCache。JDK 没有带过期、容量、刷新策略的通用缓存,ConcurrentHashMap本身并不是缓存。util.concurrent:ListenableFuture/FluentFuture/ClosingFuture。JDK 的Future无法链式回调与组合,Guava 的方案早于CompletableFuture;此外还有RateLimiter限流。hash:Hashing/BloomFilter。布隆过滤器这类概率型集合在 JDK 中完全没有对应物。io:ByteSource/ByteSink、Files、BaseEncoding、Closer(资源关闭)。graph:Graph/Network/ValueGraph(通用图模型,JDK 无)。primitives:原生类型工具与无符号类型(UnsignedLong早于 Java 8),以及ImmutableIntArray等不可变数组。math:IntMath/LongMath/Stats/Quantiles。
7. 小结
Guava 没有"重新实现 ArrayList 和 HashMap"。它的集合模块之所以存在,是因为 JDK 刻意保持了最小核心,而工程实践中大量"标准但样板极多"的集合用法长期没有现成答案。Guava 用一批 JDK 缺失的集合类型、真正不可变的集合,以及比 JDK 更省样板的工具与基础设施,把日常代码变得更短、更安全、表达力更强。
结论是:JDK 是地基,Guava 是建在地基上的扩展层------理解这一点,才能在项目里用得其所,而不是把它当成又一个"集合库"来重复造轮子。
参考:本文涉及的包与类
- 不可变集合:
com.google.common.collect.ImmutableList/ImmutableSet/ImmutableMap/ImmutableMultimap/ImmutableTable/ImmutableBiMap/ImmutableRangeSet/ImmutableRangeMap - 新集合类型:
Multimap/ListMultimap/SetMultimap/Multiset/BiMap/Table/ClassToInstanceMap - 区间:
Range/RangeSet/RangeMap/ContiguousSet/DiscreteDomain - 工具类:
Lists/Sets/Maps/Multimaps/Multisets/Queues/Tables/Iterables/Iterators/Collections2/Streams/Ordering/ComparisonChain/FluentIterable/PeekingIterator/MinMaxPriorityQueue/EvictingQueue/Interner - 其他:
base(Joiner / Splitter / Preconditions / Optional / CharMatcher / CaseFormat / Stopwatch)、cache(Cache / LoadingCache / CacheBuilder)、util.concurrent(ListenableFuture / FluentFuture / ClosingFuture / RateLimiter)、hash(Hashing / BloomFilter)、io、graph、primitives、math