Guava——基本工具(一)

基本工具

1、前置条件检查 (Preconditions)

入口防御性编程工具,用于校验方法参数和对象状态。若校验失败会抛出明确的强类型异常,支持 printf 风格的参数化消息。

底层工作机制与性能设计

  • 强类型异常抛出:内部直接通过 new 关键字抛出具体的 JDK 标准异常(如 NullPointerException、IllegalArgumentException),保持与标准库异常体系无缝兼容。
  • 延迟字符串格式化(Lazy Evaluation):使用 %s 占位符的重载版本时,消息模板与参数在校验通过时完全不进行字符串拼接。只有在校验失败抛出异常时,才会调用 Strings.lenientFormat() 进行格式化,极大地节省了正常执行路径上的内存开销与 CPU 性能。

1.1、核心 API

方法签名 失败抛出异常 适用场景 / 表达式条件
checkNotNull(T ref) NullPointerException ref != null
checkNotNull(T ref, Object errorMessage) NullPointerException ref != null(失败时打印消息字符串)
checkNotNull(T ref, String template, Object... args) NullPointerException ref != null(使用 %s 占位符格式化)
checkArgument(boolean expression) IllegalArgumentException expression == true(校验入参逻辑合法性)
checkState(boolean expression) IllegalStateException expression == true(校验对象/组件当前运行状态)
checkElementIndex(int index, int size) IndexOutOfBoundsException 0 ≤ i n d e x < s i z e 0 \le index < size 0≤index<size(校验列表/数组的元素访问下标)
checkPositionIndex(int index, int size) IndexOutOfBoundsException 0 ≤ i n d e x ≤ s i z e 0 \le index \le size 0≤index≤size(校验切片/插入时的位置下标)
checkPositionIndexes(int start, int end, int size) IndexOutOfBoundsException 0 ≤ s t a r t ≤ e n d ≤ s i z e 0 \le start \le end \le size 0≤start≤end≤size(校验子范围区间)

1.2、使用示例

java 复制代码
import com.google.common.base.Preconditions;
import java.util.List;

public class PreconditionsExample {

    private boolean isInitialized = false;

    // 1. checkArgument:检查方法传入参数的值是否合法
    public void setAge(int age) {
        // 条件不成立时抛出 IllegalArgumentException,支持带占位符的异常信息
        Preconditions.checkArgument(age >= 0 && age <= 150, "Age must be between 0 and 150, but got %s", age);
        System.out.println("Age set to: " + age);
    }

    // 2. checkNotNull:检查对象是否为空,并直接返回该对象(便于链式调用/赋值)
    public void processUser(String username) {
        // 条件不成立时抛出 NullPointerException
        String name = Preconditions.checkNotNull(username, "Username cannot be null");
        System.out.println("Processing: " + name);
    }

    // 3. checkState:检查对象的当前状态(如初始化状态、连接状态等)
    public void executeOperation() {
        // 条件不成立时抛出 IllegalStateException
        Preconditions.checkState(isInitialized, "System is not initialized yet!");
        System.out.println("Executing operation...");
    }

    // 4. checkElementIndex / checkPositionIndex:检查下标合法性
    public void accessList(List<String> list, int index) {
        Preconditions.checkNotNull(list, "List must not be null");
        
        // 检查元素索引 (0 <= index < size)
        Preconditions.checkElementIndex(index, list.size(), "Index out of bounds for access");
        System.out.println("Element: " + list.get(index));
    }

    public void init() {
        this.isInitialized = true;
    }

    public static void main(String[] args) {
        PreconditionsExample example = new PreconditionsExample();

        // 正常执行
        example.setAge(25);
        example.processUser("Alice");
        
        example.init();
        example.executeOperation();

        // 异常触发示例 (取消注释可查看对应异常):
        
        // 抛出 IllegalArgumentException: Age must be between 0 and 150, but got -5
        // example.setAge(-5); 

        // 抛出 NullPointerException: Username cannot be null
        // example.processUser(null); 
    }
}
java 复制代码
import com.google.common.base.Preconditions;
import java.util.List;

public class InventoryService {
    private boolean isInitialized = false;

    public void init() {
        this.isInitialized = true;
    }

    public void updateStock(String skuId, int quantity, List<String> warehouseList, int targetIndex) {
        // 1. 状态校验:组件未初始化时禁止调用
        Preconditions.checkState(isInitialized, "仓库服务尚未初始化,无法更新库存");

        // 2. 非空校验:配合返回值直接赋值,简化代码
        String validSkuId = Preconditions.checkNotNull(skuId, "SKU ID 不能为空");

        // 3. 参数逻辑校验:延迟格式化,仅在 quantity < 0 时进行字符串拼接
        Preconditions.checkArgument(quantity >= 0, "库存数量不能为负数,当前传入 SKU [%s] 的数量为: %s", skuId, quantity);

        // 4. 边界范围校验
        Preconditions.checkNotNull(warehouseList, "目标仓库列表不能为空");
        Preconditions.checkElementIndex(targetIndex, warehouseList.size(), "选择的仓库索引无效");

        // 业务逻辑...
    }
}

2、对象方法增强 (Objects & MoreObjects)

简化 equals、hashCode 与 toString() 的实现。

2.1、核心 API

类名 方法签名 性能/机制特性 说明与适用场景
Objects equal(@Nullable Object a, @Nullable Object b) O(1) 短路判断 内部逻辑为 a == b
Objects hashCode(@Nullable Object... objects) 自动装箱与数组创建 调用 Arrays.hashCode()。包含大量基本数据类型时会有装箱开销。
MoreObjects firstNonNull(T first, T second) O(1) 判断 返回第一个非 null 的参数。若两个均为 null 则抛出 NPE。
MoreObjects toStringHelper(Object self) 链式 StringBuilder 拼接 生成 ClassName{field1=val1, field2=val2} 格式字符串。

2.2、使用示例

java 复制代码
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;

public class Person implements Comparable<Person> {
    private String lastName;
    private String firstName;
    private Integer age;

    public Person(String lastName, String firstName, Integer age) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.age = age;
    }

    // 1. Objects.equal:安全比较,防止 NullPointerException
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equal(lastName, person.lastName) &&
               Objects.equal(firstName, person.firstName) &&
               Objects.equal(age, person.age);
    }

    // 2. Objects.hashCode:多字段哈希计算
    @Override
    public int hashCode() {
        return Objects.hashCode(lastName, firstName, age);
    }

    // 3. ComparisonChain:链式实现 Comparable,可读性极高
    @Override
    public int compareTo(Person that) {
        return ComparisonChain.start()
                .compare(this.lastName, that.lastName)
                .compare(this.firstName, that.firstName)
                .compare(this.age, that.age) // 支持 Comparable 类型及 null 值的自然排序
                .result();
    }

    // 4. MoreObjects.toStringHelper:优雅的 toString 拼接
    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .omitNullValues() // 可选:打印时自动忽略 null 值的字段
                .add("lastName", lastName)
                .add("firstName", firstName)
                .add("age", age)
                .toString();
    }

    public static void main(String[] args) {
        Person p1 = new Person("張", "三", 25);
        Person p2 = new Person("張", "三", 25);
        Person p3 = new Person("李", "四", null);

        // 格式化输出:Person{lastName=張, firstName=三, age=25}
        System.out.println("p1.toString(): " + p1);
        
        // 忽略 null 值输出:Person{lastName=李, firstName=四}
        System.out.println("p3.toString(): " + p3);

        // equals & hashCode 测试
        System.out.println("p1.equals(p2): " + p1.equals(p2)); // true

        // ComparisonChain 比较结果
        System.out.println("p1.compareTo(p3): " + p1.compareTo(p3)); // 负数,表示 p1 < p3
    }
}
java 复制代码
p1.toString(): Person{lastName=張, firstName=三, age=25}
p3.toString(): Person{lastName=李, firstName=四}
p1.equals(p2): true
p1.compareTo(p3): -1

3、链式比较器 (Ordering)

Ordering 实现了 java.util.Comparator 接口,采用了流畅接口设计模式(Fluent Interface),其本质是通过装饰器模式将多个底层排序行为层层包裹。

构造与链式调用的包装顺序(核心机制)

调用链式方法时,Ordering 的内部嵌套顺序与声明顺序相反:

  • 构建链:Ordering.natural().nullsFirst().reverse()
  • 实际执行逻辑:先处理 reverse(反转),再处理 nullsFirst(检查 null),最后执行 natural(自然比较)。

3.1、核心API

基类创建

方法签名 返回类型 功能与性能特点
Ordering.natural() Ordering<C> 使用元素的 compareTo 自然顺序
Ordering.usingToString() Ordering<Object> 按照 toString() 的字典序比较
Ordering.from(Comparator<T>) Ordering<T> 将普通 JDK Comparator 包装为 Ordering

链式配置

方法签名 返回类型 功能与性能特点
.reverse() Ordering<T> 翻转当前比较规则
.nullsFirst() Ordering<T> 将 null 视作小于所有非空元素(排在最前)
.nullsLast() Ordering<T> 将 null 视作大于所有非空元素(排在最后)
.onResultOf(Function<F, T>) Ordering<F> 提取对象的属性值进行比较(相当于 Key Extractor)
.compound(Comparator<T>) Ordering<T> 二级/次要排序规则(主规则相等时触发

数据操作

方法签名 返回类型 功能与性能特点
.sortedCopy(Iterable<E>) List<E> 返回一个排好序的新 ArrayList(原集合不修改)
.immutableSortedCopy(Iterable) ImmutableList<E> 返回不可变的有序列表
.greatestOf(Iterable<E>, int k) List<E> 获取前 K K K 个最大元素,内部使用 最小堆 实现,复杂度 O ( N log ⁡ K ) O(N \log K) O(NlogK)
.leastOf(Iterable<E>, int k) List<E> 获取前 K K K 个最小元素,内部使用 最大堆 实现,复杂度 O ( N log ⁡ K ) O(N \log K) O(NlogK)
.isOrdered(Iterable<E>) boolean 线性扫描 O ( N ) O(N) O(N) 判断集合是否已按该规则排好序

3.2、使用示例

ComparisonChain 是 Guava 提供的链式比较器,专为实现 Comparable 接口或编写 Comparator 设计。它的最大优势在于按顺序惰性求值(遇到第一对不相等的字段就会立即停止后续比较),从而避免了传统多字段比较时繁琐的 if-else 嵌套。

  • Short-circuiting(短路机制):一旦判定出结果,后续比较直接跳过,提升性能。
  • 简洁可读:采用链式调用,一目了然地定义比较优先级。
  • 处理 null:支持指定 null 值的排序规则(放前面还是放后面)。
java 复制代码
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ComparisonChainExample implements Comparable<ComparisonChainExample> {

    private String name;
    private int score;
    private Integer rank; // 可能为 null

    public ComparisonChainExample(String name, int score, Integer rank) {
        this.name = name;
        this.score = score;
        this.rank = rank;
    }

    @Override
    public int compareTo(ComparisonChainExample that) {
        return ComparisonChain.start()
                // 优先级 1:按分数降序排列(借助 Ordering.natural().reverse())
                .compare(this.score, that.score, Ordering.natural().reverse())
                
                // 优先级 2:按 rank 升序,且 null 排在最后 (nullsLast)
                .compare(this.rank, that.rank, Ordering.natural().nullsLast())
                
                // 优先级 3:按姓名自然字典序升序
                .compare(this.name, that.name)
                .result(); // 获取最终结果 (-1, 0, 1)
    }

    @Override
    public String toString() {
        return String.format("{name='%s', score=%d, rank=%s}", name, score, rank);
    }

    public static void main(String[] args) {
        List<ComparisonChainExample> list = new ArrayList<>();
        list.add(new ComparisonChainExample("Alice", 90, 2));
        list.add(new ComparisonChainExample("Bob", 90, 1));
        list.add(new ComparisonChainExample("Charlie", 90, null)); // rank 为 null
        list.add(new ComparisonChainExample("David", 95, 3));

        // 排序
        Collections.sort(list);

        // 逐行输出排序结果
        list.forEach(System.out::println);
    }
}
java 复制代码
{name='David', score=95, rank=3}
{name='Bob', score=90, rank=1}
{name='Alice', score=90, rank=2}
{name='Charlie', score=90, rank=null}
java 复制代码
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
import java.util.Arrays;
import java.util.List;

public class OrderingAdvancedDemo {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
                new Product("P101", "Phone", 999.0, "Electronics"),
                new Product("P102", "Laptop", 1999.0, "Electronics"),
                new Product("P103", "Desk", 199.0, "Furniture"),
                new Product("P104", "Chair", 199.0, null)
        );

        // 1. 定义 Key 提取器
        Function<Product, Double> priceExtractor = p -> p.getPrice();
        Function<Product, String> categoryExtractor = p -> p.getCategory();

        // 2. 构建多级复合比较器:
        // 主排序:按价格降序 (High -> Low)
        // 次排序:价格相同时按类别自然升序(且 null 类别排在最后)
        Ordering<Product> complexOrdering = Ordering.natural()
                .onResultOf(priceExtractor)
                .reverse() // 价格降序
                .compound(
                        Ordering.natural()
                                .nullsLast()
                                .onResultOf(categoryExtractor) // 次要规则:类别升序
                );

        // 3. 执行排序拷贝
        List<Product> sortedProducts = complexOrdering.sortedCopy(products);

        // 4. 高效提取 Top-N 极值(例如查找价格最高的前 2 个商品)
        // 比对整个 List 调用 Collections.sort 再 subList 快得多,避免了整体排序 O(N log N) 开销
        List<Product> top2Expensive = complexOrdering.leastOf(products, 2); 
    }
}

4、异常处理扩展 (Throwables)

用于统一捕获、解包与反射传递异常,解决 Java 受检异常(Checked Exception)在 Lambda 表达式与复杂回调中传递困难的问题。

4.1、核心API

方法签名 返回类型 功能与实现原理
getStackTraceAsString(Throwable t) String 调用 t.printStackTrace(StringWriter),将完整堆栈写入字符串返回。
getRootCause(Throwable t) Throwable 循环调用 t.getCause() 直至末端,提取最底层的 Root Cause。
getCausalChain(Throwable t) List<Throwable> 从外层到最内层,以 List 形式返回整个异常因果链。
throwIfInstanceOf(Throwable t, Class<X> declaredType) void 相当于 if (declaredType.isInstance(t)) throw (X) t;
throwIfUnchecked(Throwable t) void 若为 RuntimeException 或 Error 则直接强转抛出。
propagateIfPossible(Throwable t, Class<X> declaredType) void 组合校验:如果是 Unchecked 异常或指定的 X 类型异常,直接抛出。

4.2、使用示例

java 复制代码
public class ThrowablesExample {

    // 模拟一个多层嵌套异常的场景
    public static void executeTask() {
        try {
            // 最底层异常
            throw new SQLException("Database connection timeout");
        } catch (Exception e) {
            // 中间层包装
            throw new RuntimeException("Service execution failed", e);
        }
    }

    public static void main(String[] args) {
        try {
            executeTask();
        } catch (Throwable t) {

            // 1. 获取最底层的根因异常 (Root Cause)
            Throwable rootCause = Throwables.getRootCause(t);
            System.out.println("Root Cause: " + rootCause.getClass().getName() + " - " + rootCause.getMessage());

            // 2. 获取完整的异常调用链
            List<Throwable> causalChain = Throwables.getCausalChain(t);
            System.out.println("\nCausal Chain Size: " + causalChain.size());
            for (int i = 0; i < causalChain.size(); i++) {
                System.out.println("  Level " + i + ": " + causalChain.get(i).getMessage());
            }

            // 3. 将堆栈信息提取为字符串 (非常适合记录日志或存储到数据库)
            String stackTraceString = Throwables.getStackTraceAsString(t);
            System.out.println("\nStack Trace String (First 2 lines):");
            String firstLine = stackTraceString.split("\\R")[0];
            System.out.println(firstLine);

            // 4. 异常传播简化 (如果不希望写繁琐的 if-else 来重新抛出 Unchecked 异常)
            // 如果 t 是 RuntimeException 或 Error,则在此处重新抛出;否则继续向下执行
            Throwables.throwIfUnchecked(t);
        }
    }
}
java 复制代码
import com.google.common.base.Throwables;
import java.io.IOException;

public class TaskExecutor {

    public void executeQuery() throws IOException {
        try {
            doNetworkAndDatabaseAccess();
        } catch (Throwable t) {
            // 1. 深度寻根:判断是否是数据库连接超时引发的根源异常
            Throwable rootCause = Throwables.getRootCause(t);
            if (rootCause instanceof java.net.SocketTimeoutException) {
                System.err.println("网络底层的 Socket 连接超时,请检查网关配置");
            }

            // 2. 获取包含全链条的异常堆栈字符串,方便直接保存至日志存储系统(如 ES 或数据库)
            String fullStack = Throwables.getStackTraceAsString(t);

            // 3. 针对受检异常分类重新抛出:如果是已知允许声明抛出的 IOException,直接抛出
            Throwables.throwIfInstanceOf(t, IOException.class);

            // 4. 若属于 RuntimeException 或 Error,原样抛出,不再重复包装
            Throwables.throwIfUnchecked(t);

            // 5. 其余未显式声明的受检异常,包装为 RuntimeException 抛出,规避编译阻碍
            throw new RuntimeException("任务执行发生未知受检异常", t);
        }
    }

    private void doNetworkAndDatabaseAccess() throws Exception {
        throw new IOException("网络请求失败", new java.net.SocketTimeoutException("Read timed out"));
    }
}

5、区间范围(Range)

Range(区间)是 Guava 提供的一个强类型、不可变且用于表示连续域(Comparable 域)范围的工具类。它比 JDK 自带的简单上下界判断更直观,完美支持开闭区间、包含关系、交集/并集计算以及区间离散化等操作。

5.1、核心概念与创建方式(开闭区间)

数学中的区间包含开区间(不包含端点,用 () 表示)和闭区间(包含端点,用 \[\] 表示)。Guava 提供了静态工厂方法来创建不同类型的 Range:

数学表示 Guava 工厂方法 说明
(a,b) Range.open(a, b) 大于 a 且小于 b(双开)
[a,b] Range.closed(a, b) 大于等于 a 且小于等于 b(双闭)
(a,b] Range.openClosed(a, b) 大于 a 且小于等于 b
[a,b) Range.closedOpen(a, b) 大于等于 a 且小于 b
(a,+∞) Range.greaterThan(a) 大于 a 的半无限区间
[a,+∞) Range.atLeast(a) 大于等于 a 的半无限区间
(−∞,b) Range.lessThan(b) 小于 b 的半无限区间
(−∞,b] Range.atMost(b) 小于等于 b 的半无限区间
(−∞,+∞) Range.all() 包含所有可能的元素

代码示例:区间创建

java 复制代码
import com.google.common.collect.Range;

// 创建 [10, 20) 的半开半闭区间(包含 10,不包含 20)
Range<Integer> range1 = Range.closedOpen(10, 20);

// 创建 (0, 100) 的开区间
Range<Integer> range2 = Range.open(0, 100);

// 创建 >= 18 的半无限区间
Range<Integer> adultRange = Range.atLeast(18);

5.2、常用操作 API

5.2.1、元素判定与比较 (contains / containsAll)
java 复制代码
Range<Integer> ageRange = Range.closed(18, 60);

// 1. 判断单值是否落在区间内
boolean canWork = ageRange.contains(25); // true
boolean isChild = ageRange.contains(12); // false

// 2. 批量判断多个值是否都在区间内
boolean allValid = ageRange.containsAll(Arrays.asList(20, 30, 50)); // true
5.2.2、区间关系判定 (encloses / isConnected)
  • 包含(encloses):判断当前区间是否完整地覆盖(包裹)另一个区间。
  • 相连(isConnected):判断两区间是否有重叠或紧挨着(如 1 , 3 1, 3 1,3 与 ( 3 , 5 ] (3, 5] (3,5] 是相连的)。
java 复制代码
Range<Integer> rangeA = Range.closed(1, 10); // [1, 10]
Range<Integer> rangeB = Range.closed(3, 7);  // [3, 7]
Range<Integer> rangeC = Range.closed(8, 15); // [8, 15]

// rangeA 包含了 rangeB
boolean isEnclosed = rangeA.encloses(rangeB); // true

// rangeB 和 rangeC 有重叠部分或相邻(3~7 与 8~15 不相连;但 [1,10] 和 [8,15] 相连)
boolean connected = rangeA.isConnected(rangeC); // true
5.2.3、区间运算:交集(intersection)与最小跨度并集(span)
java 复制代码
Range<Integer> r1 = Range.closed(1, 5);  // [1, 5]
Range<Integer> r2 = Range.open(3, 8);    // (3, 8)

// 1. 交集(Intersection):提取重叠部分
Range<Integer> intersection = r1.intersection(r2); // (3, 5]

// 2. 最小跨度(Span):计算同时能包裹 r1 和 r2 的最小连贯区间
Range<Integer> span = r1.span(r2); // [1, 8)

5.3、高级用法:区间离散化与遍历 (ContiguousSet)

Range 本身代表的是连续的范围(抽象概念,无法直接转为 List)。如果元素是离散值(如 Integer、Long、LocalDate),可以结合 DiscreteDomain 将其转化为离散集合进行迭代遍历。

java 复制代码
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;

// 将 [1, 5) 离散化为整数集合 -> {1, 2, 3, 4}
Range<Integer> range = Range.closedOpen(1, 5);
ContiguousSet<Integer> set = ContiguousSet.create(range, DiscreteDomain.integers());

for (Integer value : set) {
    System.out.println(value); // 依次输出 1, 2, 3, 4
}

// 快速获取实际离散后的首尾值
Integer head = set.first(); // 1
Integer tail = set.last();  // 4

5.4、配合 RangeMap 与 RangeSet 使用

Guava 在 Range 的基础上扩展出了非常强大的复合数据结构:

① RangeSet(区间集合)

用于存储多个互不重叠的 Range,会自动将相邻或重叠的区间合并。

java 复制代码
import com.google.common.collect.TreeRangeSet;
import com.google.common.collect.RangeSet;

RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10)); // [1, 10]
rangeSet.add(Range.closed(11, 15)); // [11, 15](紧挨着 [1, 10],自动合并为 [1, 15])
rangeSet.add(Range.closed(20, 30)); // [20, 30]

rangeSet.remove(Range.closed(5, 7)); // 移除 [5, 7],分裂为 [1, 5) 和 (7, 15]

System.out.println(rangeSet.contains(12)); // true

② RangeMap(区间映射,非常推荐)

以 Range 为 Key、普通对象为 Value 的映射结构,非常适合做分段计费、折扣计算、成绩等级划分等需求,免去写大量 if-else。

java 复制代码
import com.google.common.collect.TreeRangeMap;
import com.google.common.collect.RangeMap;

RangeMap<Integer, String> gradeMap = TreeRangeMap.create();

// 映射成绩区间到等级
gradeMap.put(Range.closedOpen(0, 60), "不及格");
gradeMap.put(Range.closedOpen(60, 80), "良好");
gradeMap.put(Range.closed(80, 100), "优秀");

// 查表,免去繁琐的 if-else
String level = gradeMap.get(85); // "优秀"
String fail = gradeMap.get(45);  // "不及格"

6、耗时统计(StopWatch)

Guava 的 Stopwatch 是一个极简且线程不安全的微型性能耗时统计工具,专门用来替换传统的 System.currentTimeMillis() 或 System.nanoTime() 差值计算。

它解决了传统方式手动计算毫秒转换繁琐以及currentTimeMillis受系统时钟漂移影响的问题(底层默认依赖高精度的 System.nanoTime() 纳秒计时)。

6.1、核心 API

方法签名 功能描述
Stopwatch.createStarted() 创建并立即启动计时器(最常用)
Stopwatch.createUnstarted() 创建一个未启动的计时器(需后续手动调用 start())
stop() 停止计时(停止后可再次调用 start() 继续累加计时)
reset() 重置计时器并将耗时归零
elapsed(TimeUnit unit) 获取从启动到当前(或停止时)的耗时,可指定返回单位(毫秒、秒等)
isRunning() 判断当前计时器是否正在运行中
toString() 自动选择最合适的单位格式化输出耗时字符串(如 2.153 ms、4.201 s)

6.2、使用示例

java 复制代码
import com.google.common.base.Stopwatch;
import java.util.concurrent.TimeUnit;

public class StopwatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建并立即开始计时
        Stopwatch stopwatch = Stopwatch.createStarted();

        // 模拟执行业务逻辑
        Thread.sleep(1200);

        // 2. 停止计时(非必须,直接调用 elapsed 也可以获取实时耗时)
        stopwatch.stop();

        // 3. 读取指定单位的耗时数值
        long millis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        long seconds = stopwatch.elapsed(TimeUnit.SECONDS);

        System.out.println("毫秒数: " + millis); // 输出约 1200
        System.out.println("秒数: " + seconds);   // 输出 1

        // 4. 打印自动优化的格式化耗时(最适合写日志)
        System.out.println("格式化日志输出: " + stopwatch); // 输出类似: 1.205 s
    }
}

6.3、高级应用场景

  • 1. 分段/累加计时(分步统计)

    java 复制代码
    Stopwatch stopwatch = Stopwatch.createStarted();
    
    // 第一阶段:查询数据库
    doDatabaseQuery();
    System.out.println("DB 查询耗时: " + stopwatch); // 输出:150.2 ms
    
    // 重新归零并开启第二阶段
    stopwatch.reset().start();
    
    // 第二阶段:远程 RPC 调用
    doRpcCall();
    System.out.println("RPC 调用耗时: " + stopwatch); // 输出:80.5 ms
  • 2. 自定义时间源(单元测试)

    在单测中为了避免真实等待(如测试超时逻辑),可以通过注入自定义的 Ticker 伪造时间流逝:

    java 复制代码
    import com.google.common.base.Ticker;
    import com.google.common.testing.FakeTicker; // 需引入 guava-testlib
    
    FakeTicker fakeTicker = new FakeTicker();
    Stopwatch stopwatch = Stopwatch.createStarted(fakeTicker);
    
    // 手动向前推进 10 秒时间,无需 Thread.sleep
    fakeTicker.advance(10, TimeUnit.SECONDS);
    
    System.out.println(stopwatch.elapsed(TimeUnit.SECONDS)); // 输出 10

6.4、注意事项

  • 线程安全:Stopwatch 是非线程安全的对象。不要将其定义为单例或多线程共享的类变量,建议作为方法局部变量使用。
  • 与 Spring StopWatch 的区别:
    • Spring StopWatch:适合复杂的多阶段任务打点统计(支持生成报告 prettyPrint(),包含各个 Task 的百分比占比)。
    • Guava Stopwatch:极轻量,重点在于高精度耗时测量与友好的 toString() 自动格式化(日志输出友好)。
相关推荐
坐吃山猪29 分钟前
JDK 8 到 JDK 21 新特性知识要点
java·开发语言·windows
kakakahahahaha1 小时前
重装系统后 D 盘没了怎么办?磁盘管理显示“存储空间保护分区”的恢复思路
windows·电脑·内容运营·软件需求
吴声子夜歌3 小时前
Guava——集合(二)
java·guava
您^_^3 小时前
DeepSeek-Harness 升级排障完全指南:三类本地残留逐个拆解
人工智能·windows·个人开发·deepseekharness·deepseekv4pro
MSTcheng.4 小时前
【Linux】Linux学习第三弹——Linux基本指令2
linux·windows·学习·ubuntu·操作系统
吴声子夜歌4 小时前
Guava——缓存
缓存·guava
还是大剑师兰特6 小时前
Windows Redis 完整下载‑安装‑配置教程
windows·redis
水饺编程6 小时前
第5章,[Win32 章节] :练习程序,BigFontPic
c语言·c++·windows·visual studio
jyOverQ19 小时前
ChatGPT Windows 客户端点击无反应、有进程却没窗口?一次 MSIX 非系统盘启动卡死的完整排查
windows·chatgpt