程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Java常用开发工具包

    • Jackson(JSON处理库)
    • FastJson2(JSON处理库)
    • Gson(JSON处理库)
    • BeanUtils(对象复制工具)
    • MapStruct(对象转换工具)
    • Guava(开发工具包)
      • 1. Guava 简介与核心价值
        • 1.1 Guava 的设计哲学与优势
        • 1.2 Guava 核心模块概览
      • 2. 环境配置与依赖引入
        • 2.1 Maven 依赖配置
        • 2.2 Gradle 依赖配置
        • 2.3 相关资源链接
      • 3. 增强的集合工具 (Collections)
        • 3.1 简化集合创建 (Static Factory Methods)
        • 3.2 不可变集合 (Immutable Collections)
        • 3.3 新集合类型
        • 3.4 集合工具类 (Lists, Sets, Maps, Iterables, Iterators)
      • 4. 字符串处理工具 (Strings)
        • 4.1 Joiner:优雅地连接字符串
        • 4.2 Splitter:强大灵活地分割字符串
        • 4.3 CharMatcher:强大的字符匹配与处理
        • 4.4 CaseFormat:命名风格转换
      • 5. 原生类型工具 (Primitives)
      • 6. I/O 操作 (IO)
      • 7. 前置条件检查 (Preconditions)
      • 8. 对象工具 (Objects, MoreObjects, ComparisonChain)
        • 8.1 Objects.equal():安全的相等性判断
        • 8.2 Objects.hashCode():便捷地计算哈希码
        • 8.3 MoreObjects.toStringHelper():优雅地生成 toString()
        • 8.4 ComparisonChain:流式实现 compareTo()
      • 9. 排序器 (Ordering)
      • 10. 并发库 (Concurrency)
        • 10.1 ListenableFuture:带回调的 Future
        • 10.2 Futures:ListenableFuture 的工具类
        • 10.3 MoreExecutors:ExecutorService 的工具类
        • 10.4 RateLimiter:平滑限流器
      • 11. 缓存 (Caching)
        • 11.1 CacheBuilder:构建缓存实例
      • 12. 事件总线 (EventBus)
      • 13. 其他实用工具
    • ThreadLocal(本地线程变量)
    • SLF4j(日志框架)
    • Lombok (注解插件)
  • 开发工具包
  • Java常用开发工具包
scholar
2024-02-05
目录

Guava(开发工具包)

# Guava(开发工具包)

前言

Guava 是 Google 推出的一套包含许多 Java 核心增强功能的开源库集合,被广泛认为是 Java 开发者的必备利器。它源于 Google 内部基础架构团队多年积累的最佳实践和工具,旨在提高 Java 开发效率、代码质量和可读性。Guava 提供了大量实用的 API 和工具类,覆盖了集合、缓存、并发、字符串处理、I/O、原生类型、前置条件检查等多个方面,有效填补了 Java 标准库 (JDK) 的一些空白,并对现有功能进行了优化和增强。掌握 Guava,如同为 Java 开发者配备了一把功能强大的瑞士军刀,能够更优雅、更高效地应对日常开发中的各种挑战。

# 1. Guava 简介与核心价值

# 1.1 Guava 的设计哲学与优势

Guava 的设计遵循一些核心原则,使其在 Java 社区备受推崇:

  • 代码简洁性与可读性: Guava 致力于减少 Java 开发中的样板代码 (Boilerplate Code)。例如,使用 ImmutableList.of() 创建不可变列表,或使用 Joiner 拼接字符串,都比 JDK 的原生方式简洁得多。
  • 可靠性与健壮性: Guava 的 API 经过精心设计和广泛测试,注重边界情况处理和防御性编程。Preconditions 类就是其强调快速失败 (Fail-Fast) 原则的体现。
  • 性能优化: Guava 的许多实现(如不可变集合、缓存)都考虑了性能因素,通常比自己简单实现的版本更高效。
  • 遵循 Java 最佳实践: Guava 的设计和实现遵循了现代 Java 的编程范式和设计模式。
  • 提升生产力: 通过提供现成的、高质量的工具,避免开发者重复“造轮子”,从而节省开发时间,提高整体效率。

# 1.2 Guava 核心模块概览

Guava 的功能非常丰富,主要可以划分为以下几个核心模块:

  1. 基础工具 (Basic utilities): 提供对象操作 (Objects, MoreObjects)、前置条件检查 (Preconditions)、排序 (Ordering)、异常处理 (Throwables) 等基础功能。
  2. 集合框架增强 (Collections): Guava 对 JDK 集合框架进行了大规模扩展,提供了不可变集合 (ImmutableCollections)、新集合类型 (Multiset, Multimap, BiMap, Table, ClassToInstanceMap, RangeSet, RangeMap) 以及强大的集合工具类 (Lists, Sets, Maps, Iterables, Iterators)。
  3. 缓存 (Caching): 提供了一个非常灵活且高性能的本地(内存)缓存实现 (Cache, LoadingCache),支持多种过期策略、自动加载、统计等功能。
  4. 并发库 (Concurrency): 扩展了 Java 的并发工具,提供了 ListenableFuture、Futures、MoreExecutors、RateLimiter 等,简化异步编程和并发控制。
  5. 字符串处理 (Strings): 提供了强大的字符串操作工具,如 Joiner, Splitter, CharMatcher, CaseFormat 等。
  6. 原生类型支持 (Primitives): 提供了对 Java 基本类型(如 int, long, boolean)的实用工具类(如 Ints, Longs, Booleans),包括数组操作、类型转换等。
  7. I/O 操作 (IO): 简化了 I/O 操作,特别是对于字节流 (ByteSource, ByteSink) 和字符流 (CharSource, CharSink) 的处理,以及资源管理。
  8. 散列 (Hashing): 提供了一致且高质量的散列函数实现 (Hashing)。
  9. 事件总线 (EventBus): 提供了一种发布-订阅模式的实现,用于组件间的解耦。
  10. 数学运算 (Math): 提供了一些 JDK Math 类未包含的数学函数(如整数溢出检查运算)。
  11. 反射 (Reflection): 提供了一些简化反射操作的工具。

# 2. 环境配置与依赖引入

要在项目中使用 Guava,需要在项目的构建文件中添加相应的依赖。

# 2.1 Maven 依赖配置

在 pom.xml 文件的 <dependencies> 部分添加以下内容:

<!-- Guava 核心库依赖 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <!-- 推荐使用官方发布的最新稳定版本 -->
    <!-- JRE 版本通常适用于 Java 8 及以上 -->
    <version>33.2.1-jre</version>
</dependency>
1
2
3
4
5
6
7
8

版本选择: 请访问 Guava 的 Maven Central (opens new window) 或 GitHub Releases (opens new window) 页面查找并确认最新的稳定版本。通常有两个主要版本:-jre (适用于 Java 8+) 和 -android (针对 Android 平台优化)。对于大多数后端 Java 项目,选择 -jre 版本。

# 2.2 Gradle 依赖配置

在 build.gradle (或 build.gradle.kts) 文件的 dependencies 块中添加:

// build.gradle (Groovy DSL)
implementation 'com.google.guava:guava:33.2.1-jre' // 使用最新稳定版

// build.gradle.kts (Kotlin DSL)
// implementation("com.google.guava:guava:33.2.1-jre")
1
2
3
4
5

# 2.3 相关资源链接

  • GitHub 官方仓库: https://github.com/google/guava (opens new window) (获取源码、提交 Issue、查看最新动态)
  • 官方 Wiki (用户指南): https://github.com/google/guava/wiki (opens new window) (详细的功能介绍和用法示例)
  • Javadoc API 文档: 可在 Maven Central 或项目构建后找到。

# 3. 增强的集合工具 (Collections)

Guava 对 Java 标准集合框架 (JCF) 进行了显著的增强和扩展,这是其最常用和最受欢迎的部分之一。

# 3.1 简化集合创建 (Static Factory Methods)

Guava 的 Lists, Sets, Maps 等工具类提供了静态工厂方法,使得创建集合实例更加简洁。

import com.google.common.collect.*; // 引入 Guava 集合相关的类
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.ArrayList; // 用于对比

/**
 * Guava 简化集合创建示例
 */
public class GuavaCollectionCreation {
    public static void main(String[] args) {
        // --- 使用 Guava 静态工厂方法 ---
        // 创建空的 ArrayList, HashSet, HashMap
        List<String> guavaList = Lists.newArrayList();
        Set<String> guavaSet = Sets.newHashSet();
        Map<String, Integer> guavaMap = Maps.newHashMap();
        System.out.println("Guava 创建的空 List: " + guavaList);

        // 创建包含初始元素的集合
        List<String> names = Lists.newArrayList("Alice", "Bob", "Charlie");
        Set<Integer> numbers = Sets.newHashSet(1, 2, 3, 2); // 重复元素 2 会被自动忽略
        Map<String, String> capitals = Maps.newHashMap(ImmutableMap.of("China", "Beijing", "USA", "Washington D.C."));

        System.out.println("Guava 创建带初始元素的 List: " + names);
        System.out.println("Guava 创建带初始元素的 Set: " + numbers); // 输出不保证顺序: [1, 2, 3]
        System.out.println("Guava 创建带初始元素的 Map: " + capitals);

        // 创建指定初始容量的集合 (有助于性能优化)
        List<String> listWithCapacity = Lists.newArrayListWithCapacity(100);
        Set<String> setWithExpectedSize = Sets.newHashSetWithExpectedSize(50);

        // --- 对比 JDK 原生方式 ---
        List<String> jdkList = new ArrayList<>();
        List<String> jdkNames = new ArrayList<>();
        jdkNames.add("Alice");
        jdkNames.add("Bob");
        jdkNames.add("Charlie");
        System.out.println("JDK 创建带初始元素的 List: " + jdkNames);
        // Guava 的方式通常更简洁
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 3.2 不可变集合 (Immutable Collections)

不可变集合是指在创建之后其内容(元素或映射关系)不能被修改的集合。Guava 的不可变集合是其核心亮点之一。

优点:

  • 线程安全: 无需同步即可在多线程环境中安全共享。
  • 防御性编程: 保护数据不被意外修改,提高代码健壮性。
  • 性能与内存效率: 通常比可变集合有更好的内存占用和访问性能(因为结构固定)。
  • 可作为常量: 非常适合用作 public static final 常量。
  • 安全的 Map Key / Set 元素: 因为不可变,可以安全地用作 Map 的键或 Set 的元素(其 hashCode 和 equals 不会改变)。

创建方式:

  1. of() 方法: 最简洁的方式,适用于已知固定数量的元素。
  2. copyOf() 方法: 从现有的可变集合或数组创建不可变副本。
  3. builder() 方法: 使用 Builder 模式,适用于元素数量较多或需要在循环中构建的情况。
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections; // 用于 JDK 的不可变视图

/**
 * Guava 不可变集合示例
 */
public class GuavaImmutableCollections {
    public static final ImmutableList<String> SUPPORTED_LANGUAGES = ImmutableList.of("Java", "Python", "Go"); // 用作常量

    public static void main(String[] args) {
        // --- 使用 of() 创建 ---
        ImmutableList<String> colors = ImmutableList.of("Red", "Green", "Blue");
        ImmutableSet<Integer> primes = ImmutableSet.of(2, 3, 5, 7, 11);
        ImmutableMap<String, Integer> ageMap = ImmutableMap.of("Alice", 30, "Bob", 25);

        System.out.println("Immutable List (of): " + colors);
        System.out.println("Immutable Set (of): " + primes);
        System.out.println("Immutable Map (of): " + ageMap);

        // 尝试修改会抛出 UnsupportedOperationException
        try {
            colors.add("Yellow");
        } catch (UnsupportedOperationException e) {
            System.out.println("\n尝试修改 ImmutableList 失败: " + e.getMessage());
        }

        // --- 使用 copyOf() 创建 ---
        List<String> mutableList = new ArrayList<>();
        mutableList.add("Apple");
        mutableList.add("Banana");
        ImmutableList<String> fruits = ImmutableList.copyOf(mutableList); // 创建副本
        System.out.println("Immutable List (copyOf): " + fruits);

        mutableList.add("Cherry"); // 修改原始 List 不影响不可变副本
        System.out.println("修改原始 List 后,Immutable List 仍为: " + fruits);

        // --- 使用 builder() 创建 ---
        ImmutableMap<Integer, String> userMap = ImmutableMap.<Integer, String>builder()
            .put(101, "Carol")
            .put(102, "David")
            .put(103, "Eve")
            .build(); // 构建不可变 Map
        System.out.println("Immutable Map (builder): " + userMap);

        // --- 与 JDK Collections.unmodifiable* 的区别 ---
        // Collections.unmodifiable* 返回的是原始集合的一个不可修改的【视图】
        // 如果原始集合被修改,视图也会反映这些变化,且视图本身不是真正线程安全的(如果原始集合非线程安全)
        List<String> sourceList = new ArrayList<>();
        sourceList.add("X");
        List<String> unmodifiableView = Collections.unmodifiableList(sourceList);
        System.out.println("JDK 不可修改视图: " + unmodifiableView);
        sourceList.add("Y"); // 修改原始 List
        System.out.println("修改原始 List 后,JDK 视图变为: " + unmodifiableView); // 视图也变了!

        // Guava ImmutableCollections 创建的是真正的【副本】,与原始集合脱离关系,且自身结构不可变。
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 3.3 新集合类型

Guava 引入了多种 JDK 标准库中没有的、用于特定场景的集合类型。

1. Multiset (多重集)

  • 概念:允许元素重复的 Set,可以看作是 Bag (袋子)。它主要用于统计元素出现的次数。
  • 行为类似 Set (忽略元素顺序),但可以添加重复元素。
  • 常用实现:HashMultiset, LinkedHashMultiset, TreeMultiset, ImmutableMultiset。
import com.google.common.collect.Multiset;
import com.google.common.collect.HashMultiset;

/**
 * Guava Multiset 示例
 */
public class GuavaMultisetDemo {
    public static void main(String[] args) {
        // 创建 HashMultiset
        Multiset<String> wordCounts = HashMultiset.create();

        // 添加元素 (可以重复)
        wordCounts.add("apple");
        wordCounts.add("banana");
        wordCounts.add("apple");
        wordCounts.add("orange");
        wordCounts.add("banana");
        wordCounts.add("apple");

        // 获取元素的计数值
        System.out.println("apple 出现了 " + wordCounts.count("apple") + " 次"); // 输出: 3
        System.out.println("banana 出现了 " + wordCounts.count("banana") + " 次"); // 输出: 2
        System.out.println("orange 出现了 " + wordCounts.count("orange") + " 次"); // 输出: 1
        System.out.println("grape 出现了 " + wordCounts.count("grape") + " 次"); // 输出: 0

        // 获取所有不同元素的 Set 视图
        System.out.println("不同的元素: " + wordCounts.elementSet()); // 输出: [orange, apple, banana] (顺序不定)

        // 获取所有元素的总数
        System.out.println("总元素数量: " + wordCounts.size()); // 输出: 6

        // 设置元素的计数值
        wordCounts.setCount("orange", 5); // 将 orange 的计数设为 5
        System.out.println("\n设置后 orange 出现了 " + wordCounts.count("orange") + " 次"); // 输出: 5
        System.out.println("设置后总元素数量: " + wordCounts.size()); // 输出: 10 (3 + 2 + 5)

        // 移除元素
        wordCounts.remove("apple"); // 移除一个 apple
        System.out.println("移除一个 apple 后,apple 出现了 " + wordCounts.count("apple") + " 次"); // 输出: 2
        wordCounts.setCount("banana", 0); // 移除所有 banana
        System.out.println("移除所有 banana 后,banana 出现了 " + wordCounts.count("banana") + " 次"); // 输出: 0
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

2. Multimap (多重映射)

  • 概念:允许一个键关联多个值的 Map。解决了 JDK 中需要 Map<K, List<V>> 或 Map<K, Set<V>> 的繁琐。
  • get(key) 方法返回的是一个集合视图 (Collection 或 List 或 Set),即使键不存在也会返回空集合,永远不会是 null。
  • 常用实现:
    • ArrayListMultimap: 值集合是 ArrayList (允许重复,保持插入顺序)。
    • HashMultimap: 值集合是 HashSet (不允许重复,无序)。
    • LinkedListMultimap: 值集合是 LinkedList (允许重复,保持插入顺序,迭代顺序与添加顺序一致)。
    • TreeMultimap: 键和值集合都是有序的 (TreeMap, TreeSet)。
    • ImmutableListMultimap, ImmutableSetMultimap。
import com.google.common.collect.Multimap;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Guava Multimap 示例
 */
public class GuavaMultimapDemo {
    public static void main(String[] args) {
        // --- ArrayListMultimap: 值允许重复,保持插入顺序 ---
        Multimap<String, String> listMultimap = ArrayListMultimap.create();
        listMultimap.put("fruits", "apple");
        listMultimap.put("fruits", "banana");
        listMultimap.put("vegetables", "carrot");
        listMultimap.put("fruits", "apple"); // 添加重复的 apple

        System.out.println("ArrayListMultimap:");
        // get 返回 List 视图
        List<String> fruits = (List<String>) listMultimap.get("fruits");
        System.out.println("  Fruits: " + fruits); // 输出: [apple, banana, apple]
        // get 一个不存在的 key 返回空 List
        System.out.println("  Grains: " + listMultimap.get("grains")); // 输出: []

        // --- HashMultimap: 值不允许重复,无序 ---
        Multimap<String, String> setMultimap = HashMultimap.create();
        setMultimap.put("courses", "Math");
        setMultimap.put("courses", "Physics");
        setMultimap.put("courses", "Math"); // 添加重复的 Math,会被忽略

        System.out.println("\nHashMultimap:");
        // get 返回 Set 视图
        Collection<String> courses = setMultimap.get("courses");
        System.out.println("  Courses: " + courses); // 输出: [Physics, Math] (顺序不定)

        // --- 常用操作 ---
        // 检查是否包含某个键
        System.out.println("\nListMultimap 是否包含 key 'fruits': " + listMultimap.containsKey("fruits")); // true
        // 检查是否包含某个键值对
        System.out.println("ListMultimap 是否包含 ('fruits', 'banana'): " + listMultimap.containsEntry("fruits", "banana")); // true
        // 检查是否包含某个值
        System.out.println("ListMultimap 是否包含值 'carrot': " + listMultimap.containsValue("carrot")); // true

        // 获取所有键的 Set 视图
        System.out.println("ListMultimap 的所有键: " + listMultimap.keySet()); // 输出: [vegetables, fruits]

        // 获取所有值的 Collection 视图 (包含所有键下的所有值)
        System.out.println("ListMultimap 的所有值: " + listMultimap.values()); // 输出: [apple, banana, apple, carrot]

        // 获取 Map<K, Collection<V>> 视图
        Map<String, Collection<String>> map = listMultimap.asMap();
        System.out.println("ListMultimap 的 asMap() 视图: " + map); // 输出: {vegetables=[carrot], fruits=[apple, banana, apple]}

        // 移除某个键值对
        listMultimap.remove("fruits", "apple"); // 只移除一个 apple
        System.out.println("移除一个 apple 后 Fruits: " + listMultimap.get("fruits")); // 输出: [banana, apple]

        // 移除某个键及其所有值
        listMultimap.removeAll("vegetables");
        System.out.println("移除 vegetables 后 ListMultimap: " + listMultimap); // 输出: {fruits=[banana, apple]}

        // 替换某个键的所有值
        listMultimap.replaceValues("fruits", ImmutableList.of("grape", "mango"));
        System.out.println("替换 fruits 后 ListMultimap: " + listMultimap); // 输出: {fruits=[grape, mango]}
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

3. BiMap (双向映射)

  • 概念:保证键和值都是唯一的 Map,并提供一个 inverse() 方法来获取键值颠倒的反向 BiMap 视图。
  • 常用于需要通过值反查键的场景。
  • 如果尝试 put 一个已存在的值(关联到不同的键),或者使用 forcePut,行为取决于具体实现(HashBiMap 默认会抛异常,forcePut 会覆盖)。
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

/**
 * Guava BiMap 示例
 */
public class GuavaBiMapDemo {
    public static void main(String[] args) {
        // 创建 HashBiMap
        BiMap<String, Integer> userIdMap = HashBiMap.create();

        // 添加键值对
        userIdMap.put("Alice", 101);
        userIdMap.put("Bob", 102);
        userIdMap.put("Charlie", 103);

        System.out.println("BiMap: " + userIdMap);

        // 正向查找:通过键获取值
        System.out.println("Alice 的 ID: " + userIdMap.get("Alice")); // 输出: 101

        // 反向查找:通过值获取键
        BiMap<Integer, String> idUserMap = userIdMap.inverse(); // 获取反向视图
        System.out.println("ID 102 的用户: " + idUserMap.get(102)); // 输出: Bob

        // --- 唯一性约束 ---
        try {
            // 尝试添加已存在的值 (101) 关联到新键 David,会失败
            userIdMap.put("David", 101);
        } catch (IllegalArgumentException e) {
            System.out.println("\n添加重复值失败: " + e.getMessage());
            // 输出: 添加重复值失败: value already present: 101
        }

        // 使用 forcePut 强制插入,会移除旧的键值对 ("Alice", 101)
        userIdMap.forcePut("David", 101);
        System.out.println("forcePut 后 BiMap: " + userIdMap); // 输出: {Bob=102, Charlie=103, David=101}
        System.out.println("forcePut 后反向 BiMap: " + userIdMap.inverse()); // 输出: {102=Bob, 103=Charlie, 101=David}
        System.out.println("Alice 是否还在 Map 中: " + userIdMap.containsKey("Alice")); // 输出: false
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

4. Table (二维表结构)

  • 概念:类似于数据库表或 Excel 表格,使用两个键(行键 RowKey 和列键 ColumnKey)来唯一确定一个值 Value。Table<R, C, V>。
  • 提供了按行、按列查询数据的方法。
  • 常用实现:HashBasedTable, TreeBasedTable, ImmutableTable。
import com.google.common.collect.Table;
import com.google.common.collect.HashBasedTable;
import java.util.Map;

/**
 * Guava Table 示例
 */
public class GuavaTableDemo {
    public static void main(String[] args) {
        // 创建 HashBasedTable (行键: String, 列键: String, 值: Integer)
        Table<String, String, Integer> studentScores = HashBasedTable.create();

        // 添加数据 (put(rowKey, columnKey, value))
        studentScores.put("Alice", "Math", 90);
        studentScores.put("Alice", "English", 85);
        studentScores.put("Bob", "Math", 95);
        studentScores.put("Bob", "English", 88);
        studentScores.put("Charlie", "Math", 82);

        System.out.println("Table 内容:");
        System.out.println(studentScores);
        // 输出可能类似: {Alice={Math=90, English=85}, Bob={Math=95, English=88}, Charlie={Math=82}}

        // --- 数据访问 ---
        // 获取特定单元格的值 (get(rowKey, columnKey))
        Integer aliceMathScore = studentScores.get("Alice", "Math");
        System.out.println("\nAlice 的数学成绩: " + aliceMathScore); // 输出: 90
        // 获取不存在的值返回 null
        System.out.println("Alice 的物理成绩: " + studentScores.get("Alice", "Physics")); // 输出: null

        // 检查是否包含特定单元格
        System.out.println("是否包含 Bob 的英语成绩: " + studentScores.contains("Bob", "English")); // true

        // 获取某一行的数据 (返回 Map<ColumnKey, Value>)
        Map<String, Integer> aliceScores = studentScores.row("Alice");
        System.out.println("Alice 的所有成绩: " + aliceScores); // 输出: {Math=90, English=85}

        // 获取某一列的数据 (返回 Map<RowKey, Value>)
        Map<String, Integer> mathScores = studentScores.column("Math");
        System.out.println("所有学生的数学成绩: " + mathScores); // 输出: {Alice=90, Bob=95, Charlie=82}

        // 获取所有行键的 Set
        System.out.println("所有学生: " + studentScores.rowKeySet()); // 输出: [Alice, Bob, Charlie]
        // 获取所有列键的 Set
        System.out.println("所有科目: " + studentScores.columnKeySet()); // 输出: [Math, English]
        // 获取所有值的 Collection
        System.out.println("所有成绩: " + studentScores.values()); // 输出: [90, 85, 95, 88, 82]

        // --- 数据修改 ---
        // 移除特定单元格
        studentScores.remove("Charlie", "Math");
        System.out.println("\n移除 Charlie 的数学成绩后 Table:");
        System.out.println(studentScores);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

5. 其他特殊集合:

  • ClassToInstanceMap: 按 Class 类型存储和获取实例,保证每种类型只有一个实例。
  • RangeSet: 处理不相连或相连的区间集合。
  • RangeMap: 将不相交的、非空的区间映射到特定值。

# 3.4 集合工具类 (Lists, Sets, Maps, Iterables, Iterators)

除了创建集合和新集合类型,Guava 还提供了大量用于操作现有集合的静态工具方法。

import com.google.common.collect.*;
import com.google.common.base.Function; // Guava Function
import com.google.common.base.Predicate; // Guava Predicate
import java.util.List;
import java.util.Map;
import java.util.Collections;

/**
 * Guava 集合工具类示例
 */
public class GuavaCollectionUtilsDemo {
    public static void main(String[] args) {
        List<String> names = Lists.newArrayList("Alice", "Bob", "Charlie", "David");
        List<Integer> numbers = Lists.newArrayList(1, 2, 3, 4, 5);

        // --- Lists ---
        // 反转 List
        List<String> reversedNames = Lists.reverse(names);
        System.out.println("反转后的 List: " + reversedNames); // 输出: [David, Charlie, Bob, Alice] (这是一个视图)
        // 分区 List
        List<List<String>> partitions = Lists.partition(names, 2); // 每 2 个元素一组
        System.out.println("分区后的 List: " + partitions); // 输出: [[Alice, Bob], [Charlie, David]]

        // --- Iterables --- (适用于任何 Iterable, 包括 List, Set 等)
        // 过滤 (Filter)
        Iterable<String> namesStartingWithA = Iterables.filter(names, new Predicate<String>() {
            @Override
            public boolean apply(String input) {
                return input.startsWith("A");
            }
        });
        System.out.println("以 A 开头的名字: " + Lists.newArrayList(namesStartingWithA)); // 输出: [Alice]

        // 转换 (Transform)
        Iterable<Integer> nameLengths = Iterables.transform(names, new Function<String, Integer>() {
            @Override
            public Integer apply(String input) {
                return input.length();
            }
        });
        System.out.println("名字的长度: " + Lists.newArrayList(nameLengths)); // 输出: [5, 3, 7, 5]

        // 获取第一个元素,如果为空则返回默认值
        String first = Iterables.getFirst(names, "Default");
        System.out.println("第一个名字: " + first); // 输出: Alice
        // 获取最后一个元素
        String last = Iterables.getLast(names);
        System.out.println("最后一个名字: " + last); // 输出: David

        // 连接多个 Iterable
        Iterable<Integer> combined = Iterables.concat(numbers, Lists.newArrayList(6, 7));
        System.out.println("连接后的 Iterable: " + Lists.newArrayList(combined)); // 输出: [1, 2, 3, 4, 5, 6, 7]

        // 获取 Iterable 的大小 (比迭代计数高效)
        System.out.println("名字列表的大小: " + Iterables.size(names)); // 输出: 4

        // --- Maps ---
        Map<String, Integer> scores = ImmutableMap.of("Alice", 90, "Bob", 85, "Charlie", 95);
        // 创建 Map 的不可变视图
        // Map<String, Integer> unmodifiable = Maps.unmodifiableNavigableMap(new TreeMap<>(scores));

        // 根据 Value 过滤 Map (创建新 Map)
        Map<String, Integer> highScores = Maps.filterValues(scores, new Predicate<Integer>() {
            @Override
            public boolean apply(Integer input) {
                return input >= 90;
            }
        });
        System.out.println("分数 >= 90 的 Map: " + highScores); // 输出: {Alice=90, Charlie=95}

        // 转换 Map 的 Value
        Map<String, String> scoreGrades = Maps.transformValues(scores, new Function<Integer, String>() {
            @Override
            public String apply(Integer score) {
                return score >= 90 ? "A" : (score >= 80 ? "B" : "C");
            }
        });
        System.out.println("分数对应的等级: " + scoreGrades); // 输出: {Alice=A, Bob=B, Charlie=A}

        // 两个 Map 的差异比较 (见 5.2 节)
        // MapDifference<String, Integer> diff = Maps.difference(map1, map2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 4. 字符串处理工具 (Strings)

Guava 的字符串处理工具极大地简化了常见的字符串操作。

# 4.1 Joiner:优雅地连接字符串

Joiner 用于将集合、数组或一系列对象连接成一个字符串,可以方便地处理分隔符和 null 值。

import com.google.common.base.Joiner;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Guava Joiner 示例
 */
public class GuavaJoinerDemo {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("Apple", "Banana", "Orange");
        List<String> itemsWithNull = Arrays.asList("Apple", null, "Orange", null, "Banana");
        Map<String, String> config = ImmutableMap.of("host", "localhost", "port", "8080", "user", "admin");

        // 1. 基本用法:使用指定分隔符连接集合
        String joinedItems = Joiner.on(", ").join(items); // 使用 ", " 作为分隔符
        System.out.println("基本连接: " + joinedItems); // 输出: Apple, Banana, Orange

        // 2. 处理 null 值:跳过 null (skipNulls)
        String joinedSkipNulls = Joiner.on(" - ").skipNulls().join(itemsWithNull);
        System.out.println("跳过 null: " + joinedSkipNulls); // 输出: Apple - Orange - Banana

        // 3. 处理 null 值:替换 null (useForNull)
        String joinedReplaceNulls = Joiner.on(" | ").useForNull("N/A").join(itemsWithNull);
        System.out.println("替换 null: " + joinedReplaceNulls); // 输出: Apple | N/A | Orange | N/A | Banana

        // 4. 连接 Map:指定键值分隔符 (withKeyValueSeparator)
        String joinedMap = Joiner.on("; ").withKeyValueSeparator(" = ").join(config);
        System.out.println("连接 Map: " + joinedMap); // 输出: host = localhost; port = 8080; user = admin

        // 5. 连接数组或可变参数
        String joinedArray = Joiner.on(",").join(new String[]{"one", "two", "three"});
        System.out.println("连接数组: " + joinedArray); // 输出: one,two,three
        String joinedVarargs = Joiner.on(" ").join("Hello", "Guava", "Joiner");
        System.out.println("连接可变参数: " + joinedVarargs); // 输出: Hello Guava Joiner

        // 6. 追加到 Appendable (如 StringBuilder)
        StringBuilder sb = new StringBuilder("Config: ");
        Joiner.on(", ").withKeyValueSeparator(":").appendTo(sb, config);
        System.out.println("追加到 StringBuilder: " + sb.toString()); // 输出: Config: host:localhost, port:8080, user:admin
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 4.2 Splitter:强大灵活地分割字符串

Splitter 用于将字符串分割成子串集合(通常是 List 或 Map),提供了丰富的配置选项来处理空格、空字符串等。

import com.google.common.base.Splitter;
import java.util.List;
import java.util.Map;

/**
 * Guava Splitter 示例
 */
public class GuavaSplitterDemo {
    public static void main(String[] args) {
        String input = "apple,banana,,orange , guava ";

        // 1. 基本分割:按逗号分割
        List<String> basicSplit = Splitter.on(',').splitToList(input);
        System.out.println("基本分割: " + basicSplit);
        // 输出: [apple, banana, , orange ,  guava ] (包含空字符串和空格)

        // 2. 链式配置:移除结果空格、忽略空字符串
        List<String> cleanedSplit = Splitter.on(',')
            .trimResults()          // 移除每个结果字符串前后的空格
            .omitEmptyStrings()     // 忽略分割后产生的空字符串
            .splitToList(input);
        System.out.println("清理后分割: " + cleanedSplit);
        // 输出: [apple, banana, orange, guava]

        // 3. 使用固定长度分割
        String fixedLengthInput = "1234567890";
        List<String> fixedLengthSplit = Splitter.fixedLength(3).splitToList(fixedLengthInput);
        System.out.println("固定长度分割: " + fixedLengthSplit); // 输出: [123, 456, 789, 0] (最后一段不足3位)

        // 4. 使用正则表达式分割
        String regexInput = "apple   banana\torange";
        List<String> regexSplit = Splitter.onPattern("\\s+").splitToList(regexInput); // 按一个或多个空白符分割
        System.out.println("正则分割: " + regexSplit); // 输出: [apple, banana, orange]

        // 5. 限制分割次数 (limit)
        String limitInput = "a,b,c,d,e";
        List<String> limitedSplit = Splitter.on(',').limit(3).splitToList(limitInput); // 最多分 3 段
        System.out.println("限制次数分割: " + limitedSplit); // 输出: [a, b, c,d,e] (最后一段包含剩余所有内容)

        // 6. 分割为 Map
        String mapInput = "host=localhost,port=8080,user=admin";
        Map<String, String> configMap = Splitter.on(',')
            .withKeyValueSeparator('=') // 指定键值分隔符
            .split(mapInput);          // 返回 Map<String, String>
        System.out.println("分割为 Map: " + configMap); // 输出: {host=localhost, port=8080, user=admin}

        // 结合 CharMatcher 分割
        String mixedSeparators = "apple;banana|orange,guava";
        List<String> splitByMatcher = Splitter.on(CharMatcher.anyOf(";,|")) // 使用 CharMatcher 定义分隔符
                                             .trimResults()
                                             .omitEmptyStrings()
                                             .splitToList(mixedSeparators);
        System.out.println("使用 CharMatcher 分割: " + splitByMatcher); // 输出: [apple, banana, orange, guava]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 4.3 CharMatcher:强大的字符匹配与处理

CharMatcher 是一个用于定义和操作字符(char)集合的强大工具,可以看作是对字符的断言 (Predicate)。

import com.google.common.base.CharMatcher;

/**
 * Guava CharMatcher 示例
 */
public class GuavaCharMatcherDemo {
    public static void main(String[] args) {
        String text = "Hello World 123! #Guava";

        // --- 常见的 CharMatcher 实例 ---
        CharMatcher digits = CharMatcher.digit();          // 匹配所有数字字符
        CharMatcher letters = CharMatcher.javaLetter();    // 匹配所有 Java 字母
        CharMatcher lettersOrDigits = CharMatcher.javaLetterOrDigit(); // 字母或数字
        CharMatcher whitespace = CharMatcher.whitespace(); // 匹配所有 Java 空白字符 (空格, tab, 换行等)
        CharMatcher ascii = CharMatcher.ascii();           // 匹配所有 ASCII 字符
        CharMatcher breakingWhitespace = CharMatcher.breakingWhitespace(); // 匹配可换行的空白符

        // --- 创建自定义 CharMatcher ---
        CharMatcher vowels = CharMatcher.anyOf("aeiouAEIOU"); // 匹配任意元音字母
        CharMatcher punctuation = CharMatcher.anyOf("!#?");   // 匹配指定的标点符号
        CharMatcher hexDigits = CharMatcher.inRange('0', '9')
                                          .or(CharMatcher.inRange('a', 'f'))
                                          .or(CharMatcher.inRange('A', 'F')); // 匹配十六进制数字

        // --- 使用 CharMatcher 操作字符串 ---

        // 1. retainFrom: 保留匹配的字符
        String onlyDigits = digits.retainFrom(text);
        System.out.println("只保留数字: '" + onlyDigits + "'"); // 输出: '123'

        // 2. removeFrom: 移除匹配的字符
        String noWhitespace = whitespace.removeFrom(text);
        System.out.println("移除空白: '" + noWhitespace + "'"); // 输出: 'HelloWorld123!#Guava'

        // 3. replaceFrom: 替换匹配的字符
        String replacedVowels = vowels.replaceFrom(text, '*'); // 将元音替换为 *
        System.out.println("替换元音: '" + replacedVowels + "'"); // 输出: 'H*ll* W*rld 123! #G**v*'

        // 4. trim*: 移除字符串首尾匹配的字符
        String messy = "---abc---def---";
        String trimmed = CharMatcher.is('-').trimFrom(messy); // 移除首尾的 '-'
        System.out.println("Trim 后: '" + trimmed + "'"); // 输出: 'abc---def'
        String leadingTrimmed = CharMatcher.is('-').trimLeadingFrom(messy); // 只移除开头的 '-'
        System.out.println("TrimLeading 后: '" + leadingTrimmed + "'"); // 输出: 'abc---def---'

        // 5. collapse*: 将连续匹配的字符替换为单个指定字符
        String multipleSpaces = "Hello   World   Guava";
        String collapsed = CharMatcher.whitespace().collapseFrom(multipleSpaces, ' '); // 将连续空白压缩为单个空格
        System.out.println("Collapse 后: '" + collapsed + "'"); // 输出: 'Hello World Guava'

        // 6. matches*: 检查字符串是否满足条件
        boolean allDigits = digits.matchesAllOf("12345"); // 是否所有字符都是数字
        System.out.println("'12345' 是否全是数字: " + allDigits); // 输出: true
        boolean anyVowel = vowels.matchesAnyOf(text); // 是否包含至少一个元音
        System.out.println("文本是否包含元音: " + anyVowel); // 输出: true
        boolean noneMatch = CharMatcher.is('@').matchesNoneOf(text); // 是否不包含 '@'
        System.out.println("文本是否不包含 '@': " + noneMatch); // 输出: true

        // 7. indexIn / lastIndexIn: 查找第一个/最后一个匹配字符的索引
        int firstDigitIndex = digits.indexIn(text);
        System.out.println("第一个数字的索引: " + firstDigitIndex); // 输出: 12
        int lastVowelIndex = vowels.lastIndexIn(text);
        System.out.println("最后一个元音的索引: " + lastVowelIndex); // 输出: 21 (Guava 的 a)

        // 8. countIn: 统计匹配字符的数量
        int digitCount = digits.countIn(text);
        System.out.println("文本中数字的数量: " + digitCount); // 输出: 3
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# 4.4 CaseFormat:命名风格转换

CaseFormat 用于在不同的编程命名约定(如驼峰式、下划线式)之间进行转换。

import com.google.common.base.CaseFormat;

/**
 * Guava CaseFormat 示例
 */
public class GuavaCaseFormatDemo {
    public static void main(String[] args) {
        String camelCase = "helloWorldExample";
        String underscore = "hello_world_example";
        String hyphen = "hello-world-example";
        String upperCamel = "HelloWorldExample";

        // --- 从一种格式转换为另一种格式 ---
        // 驼峰 转 下划线 (LOWER_CAMEL -> LOWER_UNDERSCORE)
        String result1 = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, camelCase);
        System.out.println(camelCase + " -> " + result1); // 输出: helloWorldExample -> hello_world_example

        // 下划线 转 驼峰 (LOWER_UNDERSCORE -> LOWER_CAMEL)
        String result2 = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, underscore);
        System.out.println(underscore + " -> " + result2); // 输出: hello_world_example -> helloWorldExample

        // 下划线 转 大驼峰 (LOWER_UNDERSCORE -> UPPER_CAMEL)
        String result3 = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, underscore);
        System.out.println(underscore + " -> " + result3); // 输出: hello_world_example -> HelloWorldExample

        // 连字符 转 上划线常量 (LOWER_HYPHEN -> UPPER_UNDERSCORE)
        String result4 = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, hyphen);
        System.out.println(hyphen + " -> " + result4); // 输出: hello-world-example -> HELLO_WORLD_EXAMPLE

        // 大驼峰 转 小驼峰 (UPPER_CAMEL -> LOWER_CAMEL)
        String result5 = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel);
        System.out.println(upperCamel + " -> " + result5); // 输出: HelloWorldExample -> helloWorldExample
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 5. 原生类型工具 (Primitives)

Guava 为 Java 的 8 种基本类型(以及 String 和数组)提供了相应的工具类(如 Ints, Longs, Floats, Doubles, Bytes, Shorts, Chars, Booleans),包含了一些 JDK 中缺失的实用方法。

import com.google.common.primitives.*; // 引入所有原生类型工具类
import java.util.List;

/**
 * Guava 原生类型工具示例
 */
public class GuavaPrimitivesDemo {
    public static void main(String[] args) {
        // --- Ints ---
        int[] intArray = {1, 2, 3, 4, 5};
        // 数组转 List
        List<Integer> intList = Ints.asList(intArray);
        System.out.println("int[] 转 List<Integer>: " + intList); // 输出: [1, 2, 3, 4, 5]
        // List 转数组
        int[] intArray2 = Ints.toArray(intList);
        // 查找元素索引
        int index = Ints.indexOf(intArray, 3);
        System.out.println("3 在数组中的索引: " + index); // 输出: 2
        // 连接数组
        int[] combinedInts = Ints.concat(intArray, new int[]{6, 7});
        System.out.println("连接后的 int 数组: " + Ints.join(", ", combinedInts)); // 输出: 1, 2, 3, 4, 5, 6, 7
        // 检查是否包含某元素
        boolean contains4 = Ints.contains(intArray, 4);
        System.out.println("数组是否包含 4: " + contains4); // 输出: true
        // 获取数组最大/最小值
        int maxInt = Ints.max(intArray);
        int minInt = Ints.min(intArray);
        System.out.println("数组最大值: " + maxInt + ", 最小值: " + minInt); // 输出: 5, 1
        // 字符串转 int (带 null/格式 检查)
        Integer parsedInt = Ints.tryParse("123"); // 成功返回 123
        Integer invalidInt = Ints.tryParse("abc"); // 失败返回 null
        System.out.println("尝试解析 '123': " + parsedInt + ", 尝试解析 'abc': " + invalidInt);

        // --- Longs, Doubles, Floats, Bytes, Shorts, Chars, Booleans 类似 ---
        // 例如 Longs
        long[] longArray = {10L, 20L, 30L};
        List<Long> longList = Longs.asList(longArray);
        boolean contains20L = Longs.contains(longArray, 20L);
        System.out.println("\nlong[] 转 List<Long>: " + longList);
        System.out.println("long 数组是否包含 20L: " + contains20L);

        // --- Bytes ---
        byte[] byteArray = {0x01, 0x02, (byte) 0xFF};
        // Bytes 有额外的十六进制转换等方法
        String hexString = Bytes.toHexString(byteArray).toUpperCase();
        System.out.println("\nbyte[] 转十六进制字符串: " + hexString); // 输出: 0102FF

        // --- UnsignedInts / UnsignedLongs ---
        // 处理无符号整数,避免负数问题
        int unsignedValue = UnsignedInts.parseUnsignedInt("4294967295"); // 2^32 - 1
        String unsignedString = UnsignedInts.toString(unsignedValue);
        System.out.println("\n无符号整数解析与转换: " + unsignedString);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 6. I/O 操作 (IO)

Guava 的 com.google.common.io 包提供了一套更现代、更易用的 I/O API,基于 Source (数据源) 和 Sink (数据汇) 的概念,简化了流操作和资源管理。

import com.google.common.io.*; // 引入 Guava IO 类
import com.google.common.base.Charsets; // 引入 Charsets
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;

/**
 * Guava I/O 操作示例
 */
public class GuavaIODemo {
    public static void main(String[] args) {
        File sourceFile = new File("source.txt");
        File targetFile = new File("target.txt");
        File linesFile = new File("lines.txt");

        try {
            // --- 文件读写 ---

            // 1. 将字符串写入文件 (使用 CharSink)
            String contentToWrite = "Hello Guava IO!\nSecond line.";
            // 创建 CharSink,指定文件和字符集
            CharSink charSink = Files.asCharSink(targetFile, Charsets.UTF_8);
            charSink.write(contentToWrite); // 写入内容 (覆盖)
            System.out.println("内容已写入到 " + targetFile.getName());

            // 2. 将 List<String> 写入文件 (按行写入)
            List<String> linesToWrite = ImmutableList.of("Line 1", "Line 2", "Line 3");
            Files.asCharSink(linesFile, Charsets.UTF_8).writeLines(linesToWrite); // 覆盖写入行
            System.out.println("行列表已写入到 " + linesFile.getName());

            // 追加内容到文件
            Files.asCharSink(targetFile, Charsets.UTF_8, FileWriteMode.APPEND).write("\nAppended line.");
            System.out.println("内容已追加到 " + targetFile.getName());

            // 3. 从文件读取内容为字符串 (使用 CharSource)
            // 创建 CharSource
            CharSource charSource = Files.asCharSource(targetFile, Charsets.UTF_8);
            String fileContent = charSource.read(); // 读取全部内容
            System.out.println("\n从 " + targetFile.getName() + " 读取内容:\n" + fileContent);

            // 4. 从文件读取内容为 List<String> (按行读取)
            List<String> readLines = Files.asCharSource(linesFile, Charsets.UTF_8).readLines();
            System.out.println("从 " + linesFile.getName() + " 读取行列表: " + readLines);

            // 5. 文件复制 (使用 ByteSource 和 ByteSink)
            // 创建 ByteSource 和 ByteSink
            ByteSource byteSource = Files.asByteSource(sourceFile);
            ByteSink byteSink = Files.asByteSink(targetFile); // 目标文件会被覆盖
            // 执行复制
            byteSource.copyTo(byteSink);
            System.out.println("\n文件已从 " + sourceFile.getName() + " 复制到 " + targetFile.getName());

            // 6. 移动文件 (重命名)
            // Files.move(sourceFile, new File("moved_source.txt"));

            // --- 资源读取 ---
            // 读取 Classpath 下的资源文件
            try {
                URL resourceUrl = Resources.getResource("my_config.properties"); // 相对于 classpath 的路径
                String resourceContent = Resources.asCharSource(resourceUrl, Charsets.UTF_8).read();
                System.out.println("\n读取 Classpath 资源 my_config.properties:\n" + resourceContent);
            } catch (IllegalArgumentException e) {
                System.out.println("\nClasspath 资源 my_config.properties 未找到。");
            }

            // --- 其他文件工具 ---
            String extension = Files.getFileExtension("image.jpg"); // 获取文件扩展名
            System.out.println("\n文件扩展名: " + extension); // 输出: jpg
            String nameWithoutExt = Files.getNameWithoutExtension("archive.tar.gz"); // 获取不含扩展名的文件名
            System.out.println("不含扩展名的文件名: " + nameWithoutExt); // 输出: archive.tar

            // 创建临时文件目录 (Guava 20.0 之后推荐使用 JDK 7 的 Files.createTempDirectory)
            // File tempDir = Files.createTempDir();
            // System.out.println("创建的临时目录: " + tempDir.getAbsolutePath());

        } catch (IOException e) {
            System.err.println("发生 I/O 错误: " + e.getMessage());
            // Guava 的 IO 操作通常会正确处理流的关闭
        } finally {
            // 清理测试文件 (可选)
            // targetFile.delete();
            // linesFile.delete();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

Guava I/O 核心概念:

  • Source (数据源): 如 ByteSource, CharSource。代表一个可读的数据来源(文件、内存、URL 等)。它们是不可变的,提供 read() 或 copyTo(Sink) 等方法。
  • Sink (数据汇): 如 ByteSink, CharSink。代表一个可写的数据目的地。提供 write() 或 copyFrom(Source) 等方法。
  • 资源管理: Guava 的 Source/Sink API 设计旨在简化资源管理(如流的关闭)。当你调用 read() 或 write() 等终端操作时,Guava 通常会自动处理流的打开和关闭。

# 7. 前置条件检查 (Preconditions)

Preconditions 是 Guava 提供的一个极其有用的静态工具类,用于在方法或构造函数开始时检查传入的参数或对象状态是否满足预期条件。这有助于实施快速失败 (Fail-Fast) 原则,让错误尽早暴露。

import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Objects; // 用于对比 Objects.requireNonNull

/**
 * Guava Preconditions 示例
 */
public class GuavaPreconditionsDemo {

    public static void processUserData(String userId, int age, List<String> roles) {
        // --- 使用 Preconditions 进行参数检查 ---

        // 1. 检查参数不为 null (checkNotNull)
        // 如果 userId 为 null,抛出 NullPointerException
        // 返回非 null 的 userId,可以直接链式调用或赋值
        String checkedUserId = Preconditions.checkNotNull(userId, "用户 ID (userId) 不能为空");

        // 2. 检查布尔表达式是否为 true (checkArgument),通常用于检查方法参数
        // 如果 age < 18,抛出 IllegalArgumentException
        Preconditions.checkArgument(age >= 18, "年龄必须大于或等于 18 岁,实际年龄: %s", age); // 支持 %s 格式化消息

        // 3. 检查状态是否满足 (checkState),通常用于检查对象内部状态
        // 假设 roles 不能是空的
        Preconditions.checkState(roles != null && !roles.isEmpty(), "用户角色列表不能为空");

        // 4. 检查集合/数组索引是否有效 (checkElementIndex)
        int indexToAccess = 0; // 假设要访问第一个角色
        // 如果 indexToAccess 无效 (>= roles.size()),抛出 IndexOutOfBoundsException
        Preconditions.checkElementIndex(indexToAccess, roles.size(), "访问的角色索引越界");
        String firstRole = roles.get(indexToAccess);

        // 5. 检查位置范围是否有效 (checkPositionIndexes),用于子列表等操作
        int start = 0;
        int end = roles.size(); // 结束索引可以等于 size
        // 如果 start 或 end 无效,抛出 IndexOutOfBoundsException
        Preconditions.checkPositionIndexes(start, end, roles.size());
        // List<String> subList = roles.subList(start, end);

        // --- 参数检查通过后,执行核心逻辑 ---
        System.out.println("开始处理用户数据:");
        System.out.println("UserID: " + checkedUserId);
        System.out.println("Age: " + age);
        System.out.println("First Role: " + firstRole);
        // ... 业务处理 ...
    }

    public static void main(String[] args) {
        try {
            processUserData("user123", 25, Lists.newArrayList("admin", "editor")); // 正常调用
            System.out.println("用户数据处理成功!");
        } catch (Exception e) {
            System.err.println("调用失败: " + e.getMessage());
        }

        System.out.println("\n--- 测试失败场景 ---");
        try {
            processUserData(null, 30, Lists.newArrayList("guest")); // userId 为 null
        } catch (Exception e) {
            System.err.println("调用失败 (userId null): " + e.getMessage());
            // 输出: 调用失败 (userId null): 用户 ID (userId) 不能为空
        }
        try {
            processUserData("user456", 15, Lists.newArrayList("reader")); // age < 18
        } catch (Exception e) {
            System.err.println("调用失败 (age invalid): " + e.getMessage());
            // 输出: 调用失败 (age invalid): 年龄必须大于或等于 18 岁,实际年龄: 15
        }
        try {
            processUserData("user789", 40, Lists.newArrayList()); // roles 为空
        } catch (Exception e) {
            System.err.println("调用失败 (roles empty): " + e.getMessage());
            // 输出: 调用失败 (roles empty): 用户角色列表不能为空
        }

        // 对比 JDK 7+ 的 Objects.requireNonNull
        String name = null;
        try {
            Objects.requireNonNull(name, "名称不能为空 (来自 Objects)");
        } catch (NullPointerException e) {
             System.err.println("\nObjects.requireNonNull 失败: " + e.getMessage());
             // 输出: Objects.requireNonNull 失败: 名称不能为空 (来自 Objects)
        }
        // Preconditions.checkNotNull 提供了类似的功能,但属于 Guava 体系
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

Preconditions 的好处:

  • 代码更简洁: 比起冗长的 if (...) throw new ... 语句更紧凑。
  • 意图更明确: checkArgument vs checkState 清晰地区分了是对方法参数的检查还是对对象内部状态的检查。
  • 统一的异常类型: 抛出标准的 IllegalArgumentException, NullPointerException, IllegalStateException, IndexOutOfBoundsException。
  • 支持格式化消息: 错误消息可以使用 %s 占位符,提高信息的可读性。

# 8. 对象工具 (Objects, MoreObjects, ComparisonChain)

Guava 提供了一些简化常见 Object 操作的工具。

# 8.1 Objects.equal():安全的相等性判断

用于比较两个对象是否相等,能正确处理 null 值,避免 NullPointerException。

import com.google.common.base.Objects; // 引入 Guava Objects

/**
 * Guava Objects.equal 示例
 */
public class GuavaObjectsEqualDemo {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = new String("hello");
        String s3 = null;
        String s4 = null;

        // --- 对比 Objects.equal 和 a.equals(b) ---
        // 使用 a.equals(b) 可能抛出 NullPointerException
        try {
            System.out.println("s1.equals(s2): " + s1.equals(s2)); // true
            System.out.println("s1.equals(s3): " + s1.equals(s3)); // false
            // System.out.println("s3.equals(s1): " + s3.equals(s1)); // 抛出 NullPointerException!
        } catch (NullPointerException e) {
            System.err.println("使用 s3.equals(s1) 抛出异常: " + e.getMessage());
        }

        // 使用 Objects.equal() 更安全
        System.out.println("\n--- 使用 Objects.equal ---");
        System.out.println("Objects.equal(s1, s2): " + Objects.equal(s1, s2)); // true
        System.out.println("Objects.equal(s1, s3): " + Objects.equal(s1, s3)); // false
        System.out.println("Objects.equal(s3, s1): " + Objects.equal(s3, s1)); // false
        System.out.println("Objects.equal(s3, s4): " + Objects.equal(s3, s4)); // true (两个 null 相等)
    }
}
// 注意:JDK 7+ 的 java.util.Objects 类也提供了类似功能的 Objects.equals(a, b) 方法。
// 如果你的项目使用 JDK 7+,可以优先考虑使用 JDK 自带的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 8.2 Objects.hashCode():便捷地计算哈希码

用于根据对象的多个字段快速生成一个合理的哈希码,常用于实现 hashCode() 方法。

import com.google.common.base.Objects; // 引入 Guava Objects

/**
 * Guava Objects.hashCode 示例
 */
public class GuavaHashCodeDemo {
    private String field1;
    private int field2;
    private boolean field3;

    public GuavaHashCodeDemo(String field1, int field2, boolean field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    // --- 传统方式实现 hashCode ---
    /*
    @Override
    public int hashCode() {
        int result = field1 != null ? field1.hashCode() : 0;
        result = 31 * result + field2;
        result = 31 * result + (field3 ? 1 : 0);
        return result;
    }
    */

    // --- 使用 Guava Objects.hashCode ---
    @Override
    public int hashCode() {
        // 传入需要参与计算哈希码的字段
        // 它能正确处理 null 值 (null 的哈希码为 0)
        return Objects.hashCode(field1, field2, field3);
    }

    // equals 方法也应该相应实现,通常配合 hashCode 使用
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        GuavaHashCodeDemo other = (GuavaHashCodeDemo) obj;
        // 使用 Guava Objects.equal 比较字段
        return Objects.equal(field1, other.field1) &&
               field2 == other.field2 &&
               field3 == other.field3;
    }

    public static void main(String[] args) {
        GuavaHashCodeDemo obj1 = new GuavaHashCodeDemo("test", 10, true);
        GuavaHashCodeDemo obj2 = new GuavaHashCodeDemo("test", 10, true);
        GuavaHashCodeDemo obj3 = new GuavaHashCodeDemo("test", 10, false);

        System.out.println("obj1 hashCode: " + obj1.hashCode());
        System.out.println("obj2 hashCode: " + obj2.hashCode());
        System.out.println("obj3 hashCode: " + obj3.hashCode());
        System.out.println("obj1 equals obj2: " + obj1.equals(obj2)); // true
        System.out.println("obj1 equals obj3: " + obj1.equals(obj3)); // false
    }
}
// 注意:JDK 7+ 的 java.util.Objects 类也提供了 Objects.hash(Object... values) 方法实现类似功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 8.3 MoreObjects.toStringHelper():优雅地生成 toString()

用于创建清晰、格式良好的 toString() 方法输出,避免手动拼接字符串。

import com.google.common.base.MoreObjects; // 引入 MoreObjects

/**
 * Guava MoreObjects.toStringHelper 示例
 */
public class GuavaToStringHelperDemo {
    private String name;
    private int age;
    private String email;
    private Address address; // 嵌套对象

    public GuavaToStringHelperDemo(String name, int age, String email, Address address) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.address = address;
    }

    @Override
    public String toString() {
        // 1. 创建 ToStringHelper 实例,传入类名或 this
        return MoreObjects.toStringHelper(this)
                // 2. 使用 add() 添加字段名和值
                .add("name", this.name)
                .add("age", this.age)
                // 3. 可以选择性地添加字段
                .add("email", this.email)
                // 4. addValue() 添加值,不带字段名
                // .addValue(this.address != null ? this.address.getCity() : "N/A")
                // 5. omitNullValues() 可以配置忽略 null 值的字段 (默认不忽略)
                // .omitNullValues()
                .add("address", this.address) // 会调用 address.toString()
                // 6. 最后调用 toString() 生成最终字符串
                .toString();
    }

    public static void main(String[] args) {
        Address addr = new Address("Beijing", "Chaoyang");
        GuavaToStringHelperDemo demo = new GuavaToStringHelperDemo("Alice", 30, null, addr);
        System.out.println(demo.toString());
        // 输出: GuavaToStringHelperDemo{name=Alice, age=30, email=null, address=Address{city='Beijing', district='Chaoyang'}}
        // (如果配置了 omitNullValues,则 email 字段不会出现)
    }

    // 假设 Address 类定义如下
    static class Address {
        String city;
        String district;
        public Address(String city, String district){ this.city = city; this.district = district; }
        // 为了清晰,也用 toStringHelper
        @Override public String toString() {
            return MoreObjects.toStringHelper(this).add("city", city).add("district", district).toString();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 8.4 ComparisonChain:流式实现 compareTo()

用于简化实现 Comparable 接口的 compareTo() 方法,使代码更易读、不易出错。

import com.google.common.collect.ComparisonChain; // 引入 ComparisonChain
import com.google.common.collect.Ordering; // 可能需要结合 Ordering
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Guava ComparisonChain 示例
 */
public class GuavaComparisonChainDemo implements Comparable<GuavaComparisonChainDemo> {
    private String lastName;
    private String firstName;
    private int age;

    public GuavaComparisonChainDemo(String lastName, String firstName, int age) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.age = age;
    }

    // --- 传统方式实现 compareTo ---
    /*
    @Override
    public int compareTo(GuavaComparisonChainDemo other) {
        int lastNameCompare = this.lastName.compareTo(other.lastName);
        if (lastNameCompare != 0) {
            return lastNameCompare;
        }
        int firstNameCompare = this.firstName.compareTo(other.firstName);
        if (firstNameCompare != 0) {
            return firstNameCompare;
        }
        return Integer.compare(this.age, other.age);
    }
    */

    // --- 使用 Guava ComparisonChain ---
    @Override
    public int compareTo(GuavaComparisonChainDemo other) {
        // 1. 开始比较链
        return ComparisonChain.start()
                // 2. 按顺序比较字段,第一个不相等的字段决定结果
                .compare(this.lastName, other.lastName) // 比较 lastName
                .compare(this.firstName, other.firstName) // 如果 lastName 相等,比较 firstName
                .compare(this.age, other.age) // 如果前两者都相等,比较 age
                // 可以使用自定义 Comparator
                // .compare(this.someField, other.someField, Ordering.natural().nullsFirst())
                // 3. 获取比较结果
                .result();
    }

    @Override
    public String toString() { // 用于打印结果
        return MoreObjects.toStringHelper(this).add("lastName", lastName).add("firstName", firstName).add("age", age).toString();
    }

    public static void main(String[] args) {
        List<GuavaComparisonChainDemo> people = Arrays.asList(
            new GuavaComparisonChainDemo("Smith", "John", 30),
            new GuavaComparisonChainDemo("Doe", "Jane", 25),
            new GuavaComparisonChainDemo("Smith", "Alice", 28),
            new GuavaComparisonChainDemo("Doe", "John", 30)
        );

        System.out.println("排序前: " + people);
        Collections.sort(people); // 使用实现的 compareTo 方法排序
        System.out.println("排序后: " + people);
        // 预期排序结果: Doe(Jane, 25), Doe(John, 30), Smith(Alice, 28), Smith(John, 30)
        // (先按姓,再按名,最后按年龄)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 9. 排序器 (Ordering)

Ordering<T> 是 Guava 对 java.util.Comparator<T> 的强大扩展,提供了更丰富的比较器构建和操作功能。

import com.google.common.collect.Ordering;
import com.google.common.base.Function;
import com.google.common.primitives.Ints; // 用于比较 int
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Comparator; // 引入 Comparator
import lombok.AllArgsConstructor; // Lombok
import lombok.Getter; // Lombok
import lombok.ToString; // Lombok

/**
 * Guava Ordering 示例
 */
public class GuavaOrderingDemo {

    @Getter @AllArgsConstructor @ToString
    static class Person {
        String name;
        Integer age; // 使用 Integer 以便演示 null 处理
    }

    public static void main(String[] args) {
        List<String> names = Lists.newArrayList("Charlie", "Alice", "Bob", null, "David");
        List<Integer> numbers = Lists.newArrayList(5, null, 1, 8, 3, null);
        List<Person> people = Lists.newArrayList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 30), // 年龄与 Alice 相同
            new Person("David", null)  // age 为 null
        );

        // --- 创建 Ordering 实例 ---
        // 1. 自然排序
        Ordering<String> naturalStringOrder = Ordering.natural();
        Ordering<Integer> naturalIntOrder = Ordering.natural();

        // 2. 使用 toString() 排序
        Ordering<Object> toStringOrder = Ordering.usingToString();

        // 3. 基于 Function 的结果排序 (onResultOf)
        Ordering<Person> byNameLength = Ordering.natural().onResultOf(new Function<Person, Integer>() {
            @Override
            public Integer apply(Person person) {
                return person.getName().length();
            }
        });

        // --- 修改排序行为 ---
        // 4. 反转排序 (reverse)
        Ordering<String> reverseNatural = naturalStringOrder.reverse();

        // 5. null 值处理 (nullsFirst / nullsLast)
        Ordering<String> nullsFirstOrder = naturalStringOrder.nullsFirst();
        Ordering<Integer> nullsLastOrder = naturalIntOrder.nullsLast();

        // --- 复合排序 (compound) ---
        // 6. 先按年龄排序 (nullsLast),年龄相同再按姓名排序
        Ordering<Person> byAgeThenName = Ordering.natural()
            .nullsLast() // 年龄为 null 的排在后面
            .onResultOf(new Function<Person, Integer>() { // 提取年龄进行比较
                @Override public Integer apply(Person person) { return person.getAge(); }
            })
            .compound(Ordering.natural().onResultOf(Person::getName)); // 年龄相同时,按姓名自然排序

        // --- 应用 Ordering ---
        // 7. 排序集合 (直接修改原 List)
        Collections.sort(names, nullsFirstOrder);
        System.out.println("按自然顺序排序 (nulls first): " + names); // [null, Alice, Bob, Charlie, David]

        // 8. 获取排序后的副本 (sortedCopy)
        List<Integer> sortedNumbers = nullsLastOrder.sortedCopy(numbers);
        System.out.println("排序后的数字副本 (nulls last): " + sortedNumbers); // [1, 3, 5, 8, null, null]

        // 9. 判断集合是否有序 (isOrdered / isStrictlyOrdered)
        boolean isNaturalOrder = naturalStringOrder.isOrdered(Arrays.asList("A", "B", "C"));
        System.out.println("['A', 'B', 'C'] 是否自然有序: " + isNaturalOrder); // true

        // 10. 获取最大/最小值 (min / max)
        String minName = nullsFirstOrder.min(names); // 在 nulls first 规则下,null 是最小值
        System.out.println("最小值 (nulls first): " + minName); // null
        Integer maxNumber = nullsLastOrder.max(numbers); // 在 nulls last 规则下,8 是最大值
        System.out.println("最大值 (nulls last): " + maxNumber); // 8

        // 11. 获取集合中最小/最大的 k 个元素 (leastOf / greatestOf)
        List<Integer> smallestTwo = naturalIntOrder.nullsLast().leastOf(numbers, 2); // 获取最小的 2 个非 null 元素
        System.out.println("最小的 2 个数字: " + smallestTwo); // [1, 3]

        // 12. 应用复合排序
        Collections.sort(people, byAgeThenName);
        System.out.println("按年龄(nulls last)再按姓名排序: " + people);
        // [Bob(25), Alice(30), Charlie(30), David(null)]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 10. 并发库 (Concurrency)

Guava 的并发库旨在简化 Java 的多线程编程,特别是异步操作。

# 10.1 ListenableFuture:带回调的 Future

ListenableFuture<V> 扩展了 JDK 的 Future<V>,允许注册回调函数(Runnable 或 FutureCallback),在异步任务完成(成功或失败)时自动执行。

import com.google.common.util.concurrent.*; // 引入 Guava 并发类
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; // 引入 TimeUnit

/**
 * Guava ListenableFuture 示例
 */
public class GuavaListenableFutureDemo {
    public static void main(String[] args) {
        // 1. 创建一个 ExecutorService,最好使用 MoreExecutors.listeningDecorator 包装
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

        // 2. 提交一个 Callable 任务,返回 ListenableFuture
        ListenableFuture<String> futureTask = service.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("异步任务开始执行...");
                TimeUnit.SECONDS.sleep(2); // 模拟耗时操作
                // return "任务执行成功!";
                throw new RuntimeException("任务执行失败!"); // 模拟失败场景
            }
        });

        // 3. 添加回调函数 (使用 Futures.addCallback)
        Futures.addCallback(
            futureTask,
            new FutureCallback<String>() {
                // 任务成功完成时调用
                @Override
                public void onSuccess(String result) {
                    System.out.println("回调: 任务成功! 结果: " + result);
                }

                // 任务失败时调用
                @Override
                public void onFailure(Throwable t) {
                    System.err.println("回调: 任务失败! 异常: " + t.getMessage());
                }
            },
            // 指定执行回调的 Executor,可以使用 MoreExecutors.directExecutor() 在当前线程执行
            // 或者使用 service (线程池) 在其他线程执行
            MoreExecutors.directExecutor() // 在提交任务的线程或任务执行线程中执行回调
            // service // 在线程池中执行回调
        );

        System.out.println("主线程继续执行...");

        // (可选) 主线程可以做其他事情,或者等待 Future 完成
        try {
             String result = futureTask.get(); // 阻塞等待结果 (仅用于演示,实际应避免阻塞)
             System.out.println("主线程获取结果: " + result);
        } catch (Exception e) {
             System.err.println("主线程获取结果失败: " + e.getCause().getMessage());
        }

        // 关闭线程池
        service.shutdown();
        try {
            service.awaitTermination(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("主线程结束。");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 10.2 Futures:ListenableFuture 的工具类

Futures 类提供了创建、组合和操作 ListenableFuture 的静态方法。

  • immediateFuture(V value): 创建一个立即完成的 Future。
  • immediateFailedFuture(Throwable throwable): 创建一个立即失败的 Future。
  • allAsList(Iterable<ListenableFuture<V>> futures): 将多个 Future 组合成一个,当所有 Future 完成后,返回一个包含所有结果的 List 的 Future。
  • successfulAsList(...): 类似 allAsList,但忽略失败的 Future(结果为 null)。
  • transform(ListenableFuture<I> input, Function<I, O> function, Executor executor): 对 Future 的结果进行同步转换。
  • catching(ListenableFuture<V> input, Class<X> exceptionType, Function<X, V> fallback, Executor executor): 捕获特定类型的异常并返回备用结果。

# 10.3 MoreExecutors:ExecutorService 的工具类

  • listeningDecorator(ExecutorService delegate): 将普通的 ExecutorService 包装成 ListeningExecutorService,使其 submit 方法能返回 ListenableFuture。
  • directExecutor(): 返回一个 Executor,它在调用 execute 的当前线程中直接执行任务,常用于执行轻量级回调。
  • shutdownNow / awaitTermination: 提供了更健壮的线程池关闭方法。

# 10.4 RateLimiter:平滑限流器

RateLimiter 基于令牌桶算法,可以平滑地限制代码的执行速率。

import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;

/**
 * Guava RateLimiter 示例
 */
public class GuavaRateLimiterDemo {
    public static void main(String[] args) {
        // 创建一个 RateLimiter,允许每秒不超过 2 个请求 (产生令牌的速率)
        RateLimiter limiter = RateLimiter.create(2.0);

        System.out.println("开始请求 (限制速率 2 QPS)...");
        long startTime = System.nanoTime();

        for (int i = 1; i <= 10; i++) {
            // acquire() 会阻塞,直到获取到所需数量的令牌 (默认为 1)
            double waitTime = limiter.acquire(); // 获取 1 个令牌
            long currentTime = System.nanoTime();
            System.out.printf("请求 %d: 获取到令牌 (等待 %.3f 秒), 时间戳: %.3f 秒%n",
                              i, waitTime, (currentTime - startTime) / 1e9);
            // 模拟处理请求
            // ...
        }
        System.out.println("所有请求完成。");

        // RateLimiter 支持预热期 (smooth warming up)
        // RateLimiter warmingUpLimiter = RateLimiter.create(5.0, 1, TimeUnit.SECONDS);
        // 前 1 秒内速率从 0 缓慢增加到 5 QPS

        // 也支持非阻塞尝试获取令牌 tryAcquire()
        // boolean acquired = limiter.tryAcquire(1, 500, TimeUnit.MILLISECONDS); // 尝试在 500ms 内获取 1 个令牌
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 11. 缓存 (Caching)

Guava Caching 提供了一个强大、灵活的本地(JVM 内存)缓存解决方案。

# 11.1 CacheBuilder:构建缓存实例

通过链式调用 CacheBuilder.newBuilder() 来配置缓存的各种特性。

import com.google.common.cache.*; // 引入 Guava Cache 相关类
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Callable;
import lombok.AllArgsConstructor; // Lombok
import lombok.ToString; // Lombok

/**
 * Guava Cache 示例
 */
public class GuavaCacheDemo {

    // 模拟数据源
    @AllArgsConstructor @ToString
    static class User { String id; String name; }
    private static User loadUserFromDatabase(String userId) {
        System.out.println("--- 模拟从数据库加载用户: " + userId + " ---");
        // 实际应查询数据库或调用远程服务
        return new User(userId, "User_" + userId.toUpperCase());
    }

    public static void main(String[] args) throws InterruptedException {

        // --- 1. 使用 CacheLoader 创建自动加载缓存 (LoadingCache) ---
        LoadingCache<String, User> userLoadingCache = CacheBuilder.newBuilder()
            // 配置缓存大小:基于数量
            .maximumSize(100)
            // 配置缓存大小:基于权重 (需要提供 Weigher)
            // .maximumWeight(10000)
            // .weigher((String key, User user) -> user.name.length()) // 按名字长度计算权重
            // 配置过期策略:写入后 5 分钟过期
            .expireAfterWrite(5, TimeUnit.MINUTES)
            // 配置过期策略:最后访问后 1 分钟过期 (访问会刷新过期时间)
            // .expireAfterAccess(1, TimeUnit.MINUTES)
            // 配置定时刷新:写入后 1 小时尝试异步刷新 (即使缓存未过期)
            // .refreshAfterWrite(1, TimeUnit.HOURS)
            // 配置引用类型:弱引用键、弱引用值、软引用值
            // .weakKeys()
            // .weakValues()
            // .softValues()
            // 配置移除监听器
            .removalListener(new RemovalListener<String, User>() {
                @Override
                public void onRemoval(RemovalNotification<String, User> notification) {
                    System.out.println("[移除监听] Key: " + notification.getKey() +
                                       ", Value: " + notification.getValue() +
                                       ", 原因: " + notification.getCause());
                }
            })
            // 配置统计信息收集
            .recordStats()
            // **核心:构建 LoadingCache 需要提供 CacheLoader**
            .build(new CacheLoader<String, User>() {
                // 当缓存未命中时,调用 load 方法加载数据
                @Override
                public User load(String key) throws Exception {
                    return loadUserFromDatabase(key); // 加载数据的逻辑
                }
                // 可以重写 reload 方法实现异步刷新逻辑
                // @Override public ListenableFuture<User> reload(...) {}
            });

        System.out.println("--- LoadingCache 示例 ---");
        try {
            // 第一次获取 user1,缓存未命中,调用 load() 加载
            User user1 = userLoadingCache.get("user1");
            System.out.println("获取到: " + user1);
            // 第二次获取 user1,缓存命中,直接返回
            User user1_cached = userLoadingCache.get("user1");
            System.out.println("再次获取到: " + user1_cached);
            // 获取 user2,缓存未命中,调用 load() 加载
            User user2 = userLoadingCache.get("user2");
            System.out.println("获取到: " + user2);
        } catch (ExecutionException e) {
            System.err.println("加载缓存时出错: " + e.getCause().getMessage());
        }

        // 手动使缓存失效
        userLoadingCache.invalidate("user1");
        System.out.println("\n使 user1 失效后,缓存大小: " + userLoadingCache.size());
        try {
            // 再次获取 user1,缓存已失效,重新调用 load() 加载
            User user1_reloaded = userLoadingCache.get("user1");
            System.out.println("失效后重新获取: " + user1_reloaded);
        } catch (ExecutionException e) { /* ... */ }

        // 查看统计信息
        CacheStats stats = userLoadingCache.stats();
        System.out.println("\n缓存统计: 命中率=" + stats.hitRate() + ", 未命中率=" + stats.missRate() +
                           ", 加载次数=" + stats.loadCount() + ", 平均加载耗时(ns)=" + stats.averageLoadPenalty());


        // --- 2. 使用 Callable 创建手动加载缓存 (Cache) ---
        Cache<String, String> manualCache = CacheBuilder.newBuilder()
            .maximumSize(50)
            .expireAfterAccess(10, TimeUnit.SECONDS)
            .build(); // 不提供 CacheLoader

        System.out.println("\n--- Cache (手动加载) 示例 ---");
        try {
            // 尝试获取 key1,如果不存在,则执行 Callable 来计算值并放入缓存
            String value1 = manualCache.get("key1", new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("--- 计算 key1 的值 ---");
                    return "Value for key1";
                }
            });
            System.out.println("获取到 key1: " + value1);

            // 再次获取 key1,应该命中缓存
            String value1_cached = manualCache.get("key1", () -> "Should not be called");
            System.out.println("再次获取到 key1: " + value1_cached);

            // 使用 getIfPresent 获取,如果不存在则返回 null,不触发加载
            String value2 = manualCache.getIfPresent("key2");
            System.out.println("尝试获取 key2 (getIfPresent): " + value2); // 输出: null

            // 手动放入缓存
            manualCache.put("key3", "Value for key3");
            System.out.println("手动放入 key3 后获取: " + manualCache.getIfPresent("key3"));

        } catch (ExecutionException e) {
            System.err.println("手动加载缓存时出错: " + e.getCause().getMessage());
        }

        // 模拟等待过期
        System.out.println("\n等待 11 秒让 key1 过期...");
        TimeUnit.SECONDS.sleep(11);
        System.out.println("再次尝试获取 key1 (getIfPresent): " + manualCache.getIfPresent("key1")); // 输出: null
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132

核心配置选项:

  • maximumSize(long): 缓存的最大条目数。
  • maximumWeight(long) + weigher(Weigher): 基于权重的容量限制。
  • expireAfterWrite(duration, unit): 写入后固定时间过期。
  • expireAfterAccess(duration, unit): 最后访问后固定时间过期。
  • refreshAfterWrite(duration, unit): 写入后固定时间尝试异步刷新(需要 CacheLoader 支持 reload)。
  • weakKeys(), weakValues(), softValues(): 设置键或值的引用类型,用于内存敏感场景。
  • removalListener(RemovalListener): 监听缓存项被移除的事件。
  • recordStats(): 开启性能统计。
  • build(CacheLoader): 构建 LoadingCache。
  • build(): 构建 Cache。

核心操作:

  • get(K key) (LoadingCache): 获取值,如果不存在则加载。
  • get(K key, Callable<V> valueLoader) (Cache): 获取值,如果不存在则通过 Callable 计算。
  • getIfPresent(Object key): 获取值,如果不存在则返回 null。
  • put(K key, V value): 手动放入或更新缓存。
  • invalidate(Object key): 使单个缓存项失效。
  • invalidateAll(Iterable<?> keys) / invalidateAll(): 批量或全部失效。
  • stats(): 获取 CacheStats 统计对象。
  • asMap(): 获取缓存内容的 ConcurrentMap 视图。

# 12. 事件总线 (EventBus)

EventBus 是 Guava 提供的一个用于组件间解耦的事件发布-订阅机制。

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import lombok.AllArgsConstructor; // Lombok
import lombok.Getter; // Lombok
import lombok.ToString; // Lombok

/**
 * Guava EventBus 示例
 */
public class GuavaEventBusDemo {

    // 1. 定义事件类 (可以是任意 POJO)
    @Getter @AllArgsConstructor @ToString
    public static class OrderCreatedEvent {
        private String orderId;
        private double amount;
    }

    @Getter @AllArgsConstructor @ToString
    public static class OrderCancelledEvent {
        private String orderId;
        private String reason;
    }

    // 2. 定义事件监听器 (Subscriber)
    public static class OrderEventListener {
        // 使用 @Subscribe 注解标记处理事件的方法
        // 方法必须是 public,且只有一个参数,参数类型为要监听的事件类型
        @Subscribe
        public void handleOrderCreated(OrderCreatedEvent event) {
            System.out.println("[邮件服务] 收到订单创建事件: " + event + ". 发送确认邮件...");
            // ... 发送邮件逻辑 ...
        }

        @Subscribe
        public void handleOrderCancelled(OrderCancelledEvent event) {
            System.out.println("[短信服务] 收到订单取消事件: " + event + ". 发送通知短信...");
            // ... 发送短信逻辑 ...
        }

        // 一个监听器可以处理多种事件
        @Subscribe
        public void handleAnyOrderEvent(Object event) { // 可以监听 Object 来接收所有事件
            if (event instanceof OrderCreatedEvent || event instanceof OrderCancelledEvent) {
                 System.out.println("[审计服务] 记录订单事件: " + event.getClass().getSimpleName());
            }
        }

        // 如果 @Subscribe 方法签名不符合要求 (如多个参数),注册时会报错
        // @Subscribe public void invalidHandler(OrderCreatedEvent event, String extra) {}
    }

    public static void main(String[] args) {
        // 3. 创建 EventBus 实例
        // 默认是同步的,发布事件后,所有监听器的处理方法会在发布线程中按顺序执行
        EventBus eventBus = new EventBus("OrderEvents"); // 可以给 EventBus 一个标识符

        // 也可以创建异步 EventBus,监听器方法会在指定的 Executor 中执行
        // AsyncEventBus asyncEventBus = new AsyncEventBus(Executors.newCachedThreadPool());

        // 4. 创建并注册监听器实例
        OrderEventListener listener = new OrderEventListener();
        eventBus.register(listener); // 将监听器注册到 EventBus

        // 5. 发布事件
        System.out.println("发布订单创建事件...");
        OrderCreatedEvent createdEvent = new OrderCreatedEvent("ORD123", 99.99);
        eventBus.post(createdEvent); // 发布事件,EventBus 会查找已注册监听器中处理此类型事件的方法

        System.out.println("\n发布订单取消事件...");
        OrderCancelledEvent cancelledEvent = new OrderCancelledEvent("ORD456", "用户取消");
        eventBus.post(cancelledEvent);

        // 6. 发布一个没有监听器处理的事件
        System.out.println("\n发布一个无人监听的事件...");
        eventBus.post("一个普通的字符串事件"); // 不会有任何输出,因为没有 @Subscribe 方法处理 String 类型

        // 7. 注销监听器 (可选)
        // eventBus.unregister(listener);
        // System.out.println("\n注销监听器后再次发布事件:");
        // eventBus.post(new OrderCreatedEvent("ORD789", 10.0)); // 不会触发监听器
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

EventBus 注意事项:

  • 线程模型: 默认 EventBus 是同步的。如果监听器方法耗时较长,会阻塞发布线程。对于耗时操作,应使用 AsyncEventBus 或在 @Subscribe 方法内部进行异步处理。
  • 异常处理: 如果 @Subscribe 方法抛出异常,默认情况下异常会传播到 EventBus.post() 的调用者。可以通过自定义 SubscriberExceptionHandler 来处理监听器中的异常。
  • 继承关系: 如果发布事件 SubEvent,那么处理 SubEvent 和其父类 SuperEvent 的 @Subscribe 方法都会被调用。
  • Dead Events: 如果发布的事件没有任何 @Subscribe 方法可以处理,可以通过注册一个处理 com.google.common.eventbus.DeadEvent 的监听器来捕获这些“死信”。

# 13. 其他实用工具

Guava 还包含许多其他零散但非常有用的工具:

  • Throwables: 简化异常处理,特别是检查异常 (Checked Exception) 的传播。

    • Throwables.throwIfUnchecked(Throwable): 如果是 RuntimeException 或 Error,直接抛出,否则什么都不做。
    • Throwables.propagateIfPossible(Throwable, Class<X extends Exception>): 如果是 RuntimeException, Error 或指定的检查异常类型 X,则抛出。
    • Throwables.propagate(Throwable): 将任何 Throwable 包装成 RuntimeException 抛出(慎用)。
    • Throwables.getRootCause(Throwable): 获取异常链的根原因。
    • Throwables.getStackTraceAsString(Throwable): 获取完整的堆栈跟踪字符串。
  • Stopwatch: 简单的计时器工具,用于测量代码执行时间。

    • createStarted() / createUnstarted(): 创建计时器。
    • start() / stop() / reset(): 控制计时器状态。
    • elapsed(TimeUnit): 获取经过的时间。
  • Hashing: 提供高质量、一致的哈希函数实现(如 Murmur3, SHA-256)。

    • Hashing.goodFastHash(int minimumBits): 获取一个快速且质量不错的哈希函数。
    • Hashing.sha256(), Hashing.murmur3_128() 等。
    • 使用 Hasher 进行流式哈希计算。
  • Reflection: 提供一些简化 Java 反射操作的工具,如动态代理 (Reflection.newProxy)。

  • Invokable: 对 Method 和 Constructor 的封装,提供更方便的反射调用 API。

Guava 总结

Guava 无疑是 Java 开发者的强大盟友。它通过提供大量经过实战检验、设计优良的工具和 API,极大地提升了代码质量、可读性和开发效率。

使用 Guava 的最佳实践:

  1. 熟悉核心模块: 重点掌握集合 (Immutable*, Multimap, Multiset, Table, 工具类)、字符串 (Joiner, Splitter, CharMatcher)、Preconditions、Objects/MoreObjects、Cache 这些最常用、收益最高的部分。
  2. 优先使用 Guava 而非自己实现: 对于 Guava 已经提供了解决方案的常见问题(如 null 处理、集合操作、缓存等),优先使用 Guava 的实现,它们通常更健壮、更高效。
  3. 理解不可变性的价值: 尽可能地使用不可变集合 (ImmutableCollections) 来提高代码的安全性和可预测性,尤其是在多线程环境或作为公共 API 返回值时。
  4. 善用 Preconditions: 在方法入口处使用 Preconditions 进行参数校验,贯彻 Fail-Fast 原则。
  5. 合理选择缓存策略: 根据业务场景仔细配置 Guava Cache 的容量、过期策略、刷新机制等,避免滥用或配置不当导致内存问题。
  6. 注意版本兼容性: Guava 的版本迭代较快,有时会引入不兼容的变更(虽然 Google 尽量避免)。在升级版本时,需关注 Release Notes。
  7. 了解替代方案: 虽然 Guava 很强大,但也要了解 JDK 新版本(如 Java 8+ 的 Stream API, Optional, java.util.Objects)以及其他现代库(如 Vavr, Eclipse Collections)提供的类似或更优的功能,根据项目需求和技术栈做出选择。

总之,将 Guava 融入日常开发,能够让你编写出更简洁、更可靠、更具表现力的 Java 代码。它是值得每一位 Java 开发者投入时间学习和掌握的核心库。

编辑此页 (opens new window)
上次更新: 2025/04/06, 10:15:45
MapStruct(对象转换工具)
ThreadLocal(本地线程变量)

← MapStruct(对象转换工具) ThreadLocal(本地线程变量)→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式