深入解析 Guava Collection 工具类:提升 Java 集合操作效率

引言

任何熟悉 JDK 集合框架的程序员都深知 java.util.Collections 工具类的价值。Google Guava 在此基础上提供了更多强大的静态工具方法,适用于所有集合类型。这些工具类是 Guava 库中最成熟、最受欢迎的部分之一。本文将深入探讨 Guava 提供的各类集合工具,包括 Lists, Sets, Maps, Multisets, Multimaps, Tables 以及 Iterables 等,帮助您编写更简洁、高效的 Java 代码。

核心工具类概览

Guava 按照集合接口类型将工具方法分组到对应的工具类中:

接口类型 来源 Guava 工具类
Collection JDK Collections2
List JDK Lists
Set JDK Sets
Map JDK Maps
Queue JDK Queues
Multiset Guava Multisets
Multimap Guava Multimaps
BiMap Guava Maps
Table Guava Tables

注意:transform、filter 等功能性操作请参考 Guava 的函数式编程相关文章。

一、静态构造方法

1.1 类型推断简化

在 JDK 7 之前,创建泛型集合需要重复书写类型信息:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

Guava 提供了静态工厂方法利用泛型推断简化代码:

List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();

虽然 JDK 7 的菱形操作符(<>)使这些方法显得过时,但 Guava 的静态工厂方法在以下场景仍然有用:

1.2 预分配容量

通过具名工厂方法提高代码可读性:

// 精确容量
List<Type> exactly100 = Lists.newArrayListWithCapacity(100);

// 预期大小(内部会计算合适的容量)
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);

1.3 初始化元素

一行代码创建并初始化集合:

Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

最佳实践:通常推荐使用不可变集合(Immutable Collections)替代可变集合。

二、Iterables:惰性处理的 Iterable 工具

Guava 倾向于提供接受 Iterable 而非 Collection 的工具方法,因为许多"集合"可能并非存储在内存中(如从数据库或远程服务获取的数据),无法支持 size() 等操作而不实际加载所有元素。

2.1 核心特性

  • 惰性计算:大多数 Iterables 方法只在必要时才推进底层迭代
  • 视图返回:返回 Iterable 的方法通常返回惰性计算的视图,而非在内存中构建新集合
  • FluentIterable:Guava 12+ 提供的流式 API,包装 Iterable 提供链式操作

2.2 常用通用方法

方法 描述 示例
concat(Iterable<Iterable>) 连接多个 Iterable 的惰性视图 Iterables.concat(list1, list2)
frequency(Iterable, Object) 返回对象出现次数 Iterables.frequency(items, target)
partition(Iterable, int) 将 Iterable 分区为指定大小的块 Iterables.partition(data, 10)
getFirst(Iterable, T default) 获取第一个元素或默认值 Iterables.getFirst(items, defaultValue)
getLast(Iterable) 获取最后一个元素 Iterables.getLast(linkedSet)
getOnlyElement(Iterable) 获取唯一元素(失败快) Iterables.getOnlyElement(singletonSet)
limit(Iterable, int) 限制返回元素数量 Iterables.limit(source, 100)
// 连接多个列表
Iterable<Integer> concatenated = Iterables.concat(
    Ints.asList(1, 2, 3),
    Ints.asList(4, 5, 6)
);

// 安全获取唯一元素
String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton);

2.3 类集合操作

这些操作在输入为 Collection 时会委托给对应的 Collection 方法以提高效率:

// 添加所有元素
Iterables.addAll(targetCollection, sourceIterable);

// 检查包含
boolean contains = Iterables.contains(iterable, element);

// 获取大小(对 Collection 优化)
int size = Iterables.size(iterable);

// 转换为数组
String[] array = Iterables.toArray(iterable, String.class);

2.4 FluentIterable 流式 API

FluentIterable.from(source)
    .filter(predicate)
    .transform(function)
    .limit(10)
    .toImmutableList();

支持直接转换为不可变集合:

  • toImmutableList()
  • toImmutableSet()
  • toImmutableSortedSet(Comparator)

三、Lists:列表专用工具

3.1 列表视图操作

// 分区:将列表划分为固定大小的块
List<List<Integer>> parts = Lists.partition(countUp, 2);
// 结果:{{1, 2}, {3, 4}, {5}}

// 反转:返回列表的反转视图
List<Integer> countDown = Lists.reverse(theList);
// 注意:对于不可变列表,建议使用 ImmutableList.reverse()

3.2 静态工厂方法

Lists 提供多种 ArrayListLinkedList 的创建方法:

  • 基础创建:newArrayList(), newLinkedList()
  • 带元素创建:newArrayList(E...), newLinkedList(Iterable)
  • 容量优化:newArrayListWithCapacity(int), newArrayListWithExpectedSize(int)
  • 从迭代器创建:newArrayList(Iterator), newLinkedList(Iterable)

四、Comparators:比较器工具

4.1 查找最大值/最佳实践

比较类型 2个实例 多个实例
基本数值类型 Math.max(a, b) Ints.max(a, b, c...), Longs.max(...)
Comparable 实例 Comparators.max(a, b) Collections.max(asList(a, b, c))
自定义 Comparator Comparators.max(a, b, cmp) Collections.max(asList(a, b, c), cmp)

建议:静态导入相关方法以简化代码,如 max(asList(a, b, c))

五、Sets:集合论操作

5.1 集合论运算

Guava 提供标准集合论操作,返回 SetView(可直接作为 Set 使用):

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");

// 交集
SetView<String> intersection = Sets.intersection(primes, wordsWithPrimeLength);
// 包含:"two", "three", "seven"

// 可转换为不可变副本以提高重复使用效率
return intersection.immutableCopy();

支持的操作:

  • union(Set, Set) - 并集
  • intersection(Set, Set) - 交集
  • difference(Set, Set) - 差集
  • symmetricDifference(Set, Set) - 对称差集

5.2 其他集合工具

// 笛卡尔积
Set<List<String>> product = Sets.cartesianProduct(animals, fruits);
// 结果:{{"gerbil", "apple"}, {"gerbil", "orange"}, ...}

// 幂集(所有子集)
Set<Set<String>> animalSets = Sets.powerSet(animals);
// 结果:{{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}}

六、Maps:映射工具

6.1 uniqueIndex:唯一索引构建

将具有唯一属性的对象集合转换为基于该属性的 Map:

ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, 
    new Function<String, Integer>() {
        public Integer apply(String string) {
            return string.length();
        }
    });

注意:如果索引不唯一,请使用 Multimaps.index

6.2 difference:地图差异比较

比较两个 Map 的所有差异:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("b", 2, "c", 4, "d", 5);

MapDifference<String, Integer> diff = Maps.difference(left, right);

diff.entriesInCommon();      // {"b" => 2}
diff.entriesDiffering();     // {"c" => (3, 4)}
diff.entriesOnlyOnLeft();    // {"a" => 1}
diff.entriesOnlyOnRight();   // {"d" => 5}

6.3 BiMap 工具

BiMap 的工具方法也在 Maps 类中:

  • synchronizedBiMap(BiMap) - 同步包装
  • unmodifiableBiMap(BiMap) - 不可变包装

七、Multisets:多重集操作

标准 Collection 操作忽略元素计数,而 Multisets 提供考虑元素重数的操作:

方法 说明 与 Collection 方法的区别
containsOccurrences 检查超集是否包含子集的所有出现次数 containsAll 只检查元素是否存在
removeOccurrences 按出现次数移除元素 removeAll 移除所有匹配元素的实例
retainOccurrences 保留不超过指定次数的元素 retainAll 保留所有匹配元素的实例
intersection 返回多重集交集视图 无对应方法
Multiset<String> multiset1 = HashMultiset.create();
multiset1.add("a", 2);

Multiset<String> multiset2 = HashMultiset.create();
multiset2.add("a", 5);

multiset1.containsAll(multiset2);           // true(只检查元素存在)
Multisets.containsOccurrences(multiset1, multiset2); // false(检查计数)

Multisets.removeOccurrences(multiset2, multiset1); 
// multiset2 现在包含 3 个 "a"

其他实用方法:

  • copyHighestCountFirst(Multiset) - 按频率降序返回不可变副本
  • unmodifiableMultiset(Multiset) - 不可变视图

八、Multimaps:多重映射

8.1 index:非唯一索引

当需要根据非唯一属性分组对象时:

ImmutableSet<String> digits = ImmutableSet.of("zero", "one", "two", "three", "four", 
                                               "five", "six", "seven", "eight", "nine");

Function<String, Integer> lengthFunction = new Function<String, Integer>() {
    public Integer apply(String string) {
        return string.length();
    }
};

ImmutableListMultimap<Integer, String> digitsByLength = Multimaps.index(digits, lengthFunction);
// 结果:
// 3 => {"one", "two", "six"}
// 4 => {"zero", "four", "five", "nine"}
// 5 => {"three", "seven", "eight"}

8.2 invertFrom:反转多重映射

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));

TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(
    multimap, 
    TreeMultimap.<Integer, String>create()
);
// 结果按顺序:
// 1 => {"a"}
// 2 => {"a", "b", "c"}
// 3 => {"c"}
// ...

8.3 forMap:Map 转 Multimap

将普通 Map 视为 SetMultimap 使用:

Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap<String, Integer> multimap = Multimaps.forMap(map);
// multimap: ["a" => {1}, "b" => {1}, "c" => {2}]

Multimap<Integer, String> inverse = Multimaps.invertFrom(
    multimap, 
    HashMultimap.<Integer, String>create()
);
// inverse: [1 => {"a", "b"}, 2 => {"c"}]

8.4 自定义 Multimap 实现

ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap(
    Maps.<String, Collection<Integer>>newTreeMap(),
    new Supplier<LinkedList<Integer>>() {
        public LinkedList<Integer> get() {
            return Lists.newLinkedList();
        }
    }
);

注意事项

  • Multimap 完全拥有传入的 Map 和工厂生成的集合
  • 不提供线程安全保证(需使用同步包装)
  • get(key) 返回的集合类型可能与 Supplier 返回的类型不同

九、Tables:表格工具

9.1 customTable:自定义表格实现

// 使用 LinkedHashMap 代替 HashMap
Table<String, Character, Integer> table = Tables.newCustomTable(
    Maps.<String, Map<Character, Integer>>newLinkedHashMap(),
    new Supplier<Map<Character, Integer>>() {
        public Map<Character, Integer> get() {
            return Maps.newLinkedHashMap();
        }
    }
);

9.2 transpose:转置表格

Table<C, R, V> transposed = Tables.transpose(originalTable);
// 将 Table<R, C, V> 转换为 Table<C, R, V>

9.3 包装器

  • unmodifiableTable(Table) - 不可变表格视图
  • unmodifiableRowSortedTable(RowSortedTable) - 不可变行排序表格

建议:大多数情况下优先使用 ImmutableTable

最佳实践总结

  1. 优先使用不可变集合:除非确实需要可变性,否则使用 Guava 的不可变集合类
  2. 利用惰性求值Iterables 和视图操作避免不必要的内存分配
  3. 选择合适的数据结构:根据场景选择 Multiset、Multimap 或 Table
  4. 静态导入工具方法:简化代码,如 static import com.google.common.collect.Lists.*
  5. 注意线程安全:Guava 集合默认不是线程安全的,需要时使用同步包装
  6. 理解视图与副本:区分返回视图的操作和创建副本的操作

总结

Guava 的集合工具类极大地扩展了 JDK 集合框架的功能,提供了更丰富、更便捷的 API。通过合理使用这些工具类,您可以:

  • 减少样板代码
  • 提高代码可读性
  • 优化性能(通过惰性求值和预分配)
  • 处理复杂的数据结构需求(如多重集、多重映射、表格)
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐