1. 概述

Google Guava 是一组非常实用的 Java 工具库,极大简化了日常开发中的重复性工作。本文将带你快速了解 Guava 19 版本新增的一些功能和改进。

2. common.base 包的变更

2.1. CharMatcher 新增静态方法

CharMatcher 主要用于判断字符串是否符合某些字符规则,也可以用来做字符串清洗、替换等操作。

✅ 例如判断一个字符串是否全由字母或数字组成:

String inputString = "someString789";
boolean result = CharMatcher.javaLetterOrDigit().matchesAllOf(inputString);

结果是 true

✅ 或者用它来处理字符串中的空白字符:

String number = "8 123 456 123";
String result = CharMatcher.whitespace().collapseFrom(number, '-');

输出为 "8-123-456-123"

✅ 统计某个字符在字符串中出现的次数也很简单:

String number = "8 123 456 123";
int result = CharMatcher.digit().countIn(number);

这里返回的是 10

⚠️ 在 Guava 19 中,之前版本中的常量如 CharMatcher.WHITESPACECharMatcher.JAVA_LETTER_OR_DIGIT 被标记为过时,取而代之的是对应的静态方法:CharMatcher.whitespace()CharMatcher.javaLetterOrDigit()

这个改动是为了减少类加载时不必要的对象创建。未来这些常量会被彻底移除。

2.2. Throwables.lazyStackTrace 方法

该方法返回一个 Throwable 的堆栈信息(List<StackTraceElement>),相比传统的 getStackTrace(),它在只需要部分堆栈时效率更高。

IllegalArgumentException e = new IllegalArgumentException("Some argument is incorrect");
List<StackTraceElement> stackTraceElements = Throwables.lazyStackTrace(e);

如果你需要完整遍历堆栈,则性能可能不如直接使用 getStackTrace(),但如果你只关心前几行,那么这个方法会更高效。

3. common.collect 包的变更

3.1. 新增 FluentIterable.toMultiset() 方法

在之前的 Guava 18 文章中我们已经介绍过 FluentIterable,Guava 19 为其增加了 toMultiset() 方法,用于将 FluentIterable 转换为 ImmutableMultiset

User[] usersArray = {new User(1L, "John", 45), new User(2L, "Max", 15)};
ImmutableMultiset<User> users = FluentIterable.of(usersArray).toMultiset();

📌 Multiset 是一种支持重复元素的集合,类似 Set,但允许元素重复,并提供 count() 方法统计元素出现次数。

示例:

List<String> userNames = Arrays.asList("David", "Eugen", "Alex", "Alex", "David", "David", "David");

Multiset<String> userNamesMultiset = HashMultiset.create(userNames);

assertEquals(7, userNamesMultiset.size());
assertEquals(4, userNamesMultiset.count("David"));
assertEquals(2, userNamesMultiset.count("Alex"));
assertEquals(1, userNamesMultiset.count("Eugen"));
assertThat(userNamesMultiset.elementSet(), anyOf(containsInAnyOrder("Alex", "David", "Eugen")));

相比传统 Java 集合,这种方式统计重复项要清爽得多。

3.2. 新增 RangeSet.asDescendingSetOfRanges()asDescendingMapOfRanges() 方法

RangeSet 用于管理不相交的区间集合,当添加新区间时,如果与其他区间有交集,就会自动合并。

常用区间构建方法包括:

  • Range.closed(1, 10):闭区间 [1, 10]
  • Range.open(1, 10):开区间 (1, 10)
  • Range.closedOpen(1, 10):左闭右开 [1, 10)
  • Range.openClosed(1, 10):左开右闭 (1, 10]

示例:

RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10));
rangeSet.add(Range.closed(5, 15)); // 合并为 [1, 15]
rangeSet.add(Range.closedOpen(10, 17)); // 合并为 [1, 17)

新加入的方法允许你将 RangeSet 转换为降序排列的 SetMap

NavigableMap<Range<Integer>, Object> descendingMap = rangeSet.asDescendingMapOfRanges();
NavigableSet<Range<Integer>> descendingSet = rangeSet.asDescendingSetOfRanges();

这在某些特定排序场景下非常有用。

3.3. 新增 Lists.cartesianProduct(List…)Lists.cartesianProduct(List>) 方法

这两个方法用于生成多个集合的笛卡尔积,即所有可能的组合。

示例:

List<String> first = Lists.newArrayList("value1", "value2");
List<String> second = Lists.newArrayList("value3", "value4");

List<List<String>> cartesianProduct = Lists.cartesianProduct(first, second);

List<String> pair1 = Lists.newArrayList("value2", "value3");
List<String> pair2 = Lists.newArrayList("value2", "value4");
List<String> pair3 = Lists.newArrayList("value1", "value3");
List<String> pair4 = Lists.newArrayList("value1", "value4");

assertThat(cartesianProduct, anyOf(containsInAnyOrder(pair1, pair2, pair3, pair4)));

3.4. 新增 Maps.newLinkedHashMapWithExpectedSize(int) 方法

默认情况下 LinkedHashMap 初始容量是 16,当超过负载因子(默认 0.75)后会自动扩容。如果提前知道数据规模较大,可以通过此方法指定初始容量,避免频繁 rehash:

LinkedHashMap<Object, Object> someLinkedMap = Maps.newLinkedHashMapWithExpectedSize(512);

3.5. 重新引入 Multisets.removeOccurrences(Multiset, Multiset) 方法

用于从一个 Multiset 中移除另一个 Multiset 中存在的元素(按出现次数)。

Multiset<String> multisetToModify = HashMultiset.create();
Multiset<String> occurrencesToRemove = HashMultiset.create();

multisetToModify.add("John");
multisetToModify.add("Max");
multisetToModify.add("Alex");

occurrencesToRemove.add("Alex");
occurrencesToRemove.add("John");

Multisets.removeOccurrences(multisetToModify, occurrencesToRemove);

执行后,multisetToModify 只剩下 "Max"

⚠️ 注意:即使原集合中某个元素多次出现,也只会按目标集合中该元素的出现次数进行删除。

4. common.hash 包的变更

4.1. 新增 Hashing.sha384() 方法

提供 SHA-384 哈希算法实现:

int inputData = 15;

HashFunction hashFunction = Hashing.sha384();
HashCode hashCode = hashFunction.hashInt(inputData);

输出类似 "0904b6277381dcfbddd...2240a621b2b5e3cda8"

4.2. 新增 Hashing.concatenating(...) 方法

可以将多个哈希函数的结果拼接起来:

int inputData = 15;

HashFunction crc32Function = Hashing.crc32();
HashCode crc32HashCode = crc32Function.hashInt(inputData);

HashFunction hashFunction = Hashing.concatenating(Hashing.crc32(), Hashing.crc32());
HashCode concatenatedHashCode = hashFunction.hashInt(inputData);

结果为 "4acf27794acf2779",即两个 CRC32 结果的拼接。

📌 实际应用中建议组合不同的哈希函数以增强安全性。

5. common.reflect 包的变更

5.1. 新增 TypeToken.isSubtypeOf 方法

由于 Java 泛型存在类型擦除问题,运行时无法直接判断泛型的实际类型。TypeToken 提供了运行时检查泛型类型的能力。

例如:

ArrayList<String> stringList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
boolean isAssignableFrom = stringList.getClass().isAssignableFrom(intList.getClass());

上面这段代码会返回 true,但实际上 ArrayList<String> 并不能赋值给 ArrayList<Integer>

解决办法是使用 TypeToken

TypeToken<ArrayList<String>> listString = new TypeToken<ArrayList<String>>() { };
TypeToken<ArrayList<Integer>> integerString = new TypeToken<ArrayList<Integer>>() { };

boolean isSupertypeOf = listString.isSupertypeOf(integerString); // false

✅ 此外,还可以使用 isSubtypeOf 判断子类型关系:

TypeToken<ArrayList<String>> stringList = new TypeToken<ArrayList<String>>() { };
TypeToken<List> list = new TypeToken<List>() { };

boolean isSubtypeOf = stringList.isSubtypeOf(list); // true

📌 在 Guava 19 中,原来的 isAssignableFrom 方法已被弃用,推荐使用 isSupertypeOfisSubtypeOf

6. common.io 包的变更

6.1. 新增 ByteSource.sizeIfKnown() 方法

用于获取字节源的大小(单位:字节),如果能确定的话,不会打开流:

ByteSource charSource = Files.asByteSource(file);
Optional<Long> size = charSource.sizeIfKnown();

6.2. 新增 CharSource.length() 方法

用于获取字符源的长度(单位:字符):

CharSource charSource = Files.asCharSource(file, Charsets.UTF_8);
long length = charSource.length();

6.3. 新增 CharSource.lengthIfKnown() 方法

sizeIfKnown() 类似,但返回的是字符数而非字节数:

CharSource charSource = Files.asCharSource(file, Charsets.UTF_8);
Optional<Long> length = charSource.lengthIfKnown();

7. 总结

Guava 19 引入了许多实用的新功能和改进,特别是在类型安全、集合操作及 I/O 处理方面。对于追求简洁高效代码的开发者来说,值得在项目中尝试使用。

本文涉及的所有代码示例均可在 GitHub 仓库 中找到。


原始标题:Guava 19: What's New?

» 下一篇: RESTEasy 使用指南