基本工具
-
- 1、前置条件检查 (Preconditions)
-
- [1.1、核心 API](#1.1、核心 API)
- 1.2、使用示例
- 2、对象方法增强 (Objects & MoreObjects)
-
- [2.1、核心 API](#2.1、核心 API)
- 2.2、使用示例
- 3、链式比较器 (Ordering)
- 4、异常处理扩展 (Throwables)
- 5、区间范围(Range)
-
- 5.1、核心概念与创建方式(开闭区间)
- [5.2、常用操作 API](#5.2、常用操作 API)
- 5.3、高级用法:区间离散化与遍历 (ContiguousSet)
- [5.4、配合 RangeMap 与 RangeSet 使用](#5.4、配合 RangeMap 与 RangeSet 使用)
- 6、耗时统计(StopWatch)
-
- [6.1、核心 API](#6.1、核心 API)
- 6.2、使用示例
- 6.3、高级应用场景
- 6.4、注意事项
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. 分段/累加计时(分步统计)
javaStopwatch 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 伪造时间流逝:
javaimport 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() 自动格式化(日志输出友好)。