Java 常用工具类与异常处理:写出健壮代码的必修课

学习日期:2026-06-09

难度:⭐⭐⭐ 进阶

前言

面向对象学得差不多了,该补一波"实用工具"了。日常写代码,80% 的场景都在跟这些东西打交道:字符串处理、数组操作、日期时间、空指针防护、异常处理——这些东西不"高级",但用不熟就是写不快、容易错。

这篇把最常用的工具类和异常处理一次性讲透,都是拿来就能用的干货。

第一部分:异常处理

1.1 什么是异常?

程序运行时出的错,就是异常。Java 把异常封装成对象,统一处理。

Throwable

├── Error(严重错误,比如 OutOfMemoryError,程序救不了)

└── Exception(异常,可处理)

├── RuntimeException(运行时异常,非受检)

│ ├── NullPointerException

│ ├── ArrayIndexOutOfBoundsException

│ ├── ClassCastException

│ ├── IllegalArgumentException

│ └── ...

└── 受检异常(Checked Exception,编译时必须处理)

├── IOException

├── SQLException

└── ...

受检异常 vs 非受检异常:

表格

受检异常 运行时异常(非受检)
代表 IOException、SQLException NullPointerException、ArrayIndexOutOfBoundsException
编译要求 必须处理(try-catch 或 throws) 不强制处理
发生时机 外部不可控因素 程序逻辑错误
处理方式 捕获并恢复 修复代码 bug

💡 记住:RuntimeException 及其子类都是非受检异常,其他都是受检异常

1.2 try-catch-finally

try {

// 可能出异常的代码

int result = 10 / 0;

System.out.println("这行不会执行");

} catch (ArithmeticException e) {

// 捕获并处理

System.out.println("除数不能为零:" + e.getMessage());

} finally {

// 无论是否异常,都会执行(除非 JVM 退出)

System.out.println("finally 必执行");

}

执行流程:

  • try 中正常 → 跳过 catch,执行 finally
  • try 中异常 → 跳到 catch,执行完 catch 再执行 finally
  • finally 中不要写 return,会覆盖 try/catch 中的返回值

⚠️ 经典坑点:finally 中写 return

int test() {

try {

return 1;

} finally {

return 2; // ❌ 会覆盖 try 的返回值!最终返回 2

}

}

1.3 多重 catch

try {

String str = null;

System.out.println(str.length()); // NPE

int[] arr = new int[2];

arr[5] = 10; // 数组越界

} catch (NullPointerException e) {

System.out.println("空指针异常");

} catch (ArrayIndexOutOfBoundsException e) {

System.out.println("数组越界");

} catch (Exception e) { // 兜底,放最后

System.out.println("其他异常:" + e.getMessage());

}

规则:子类异常 catch 放前面,父类放后面。否则前面的父类已经捕获了,子类 catch 永远走不到。

1.4 throws 和 throw

throws——方法声明时抛出异常,甩锅给调用者:

// 受检异常必须声明 throws

public void readFile(String path) throws IOException, FileNotFoundException {

FileInputStream fis = new FileInputStream(path);

// ...

}

throw——手动抛出异常:

public void setAge(int age) {

if (age < 0 || age > 150) {

throw new IllegalArgumentException("年龄不合法:" + age);

}

this.age = age;

}

💡 重写方法的 throws 规则:子类重写父类方法时,抛出的异常不能比父类更宽泛(可以更小或不抛)。父类抛 IOException,子类不能抛 Exception。

1.5 try-with-resources 自动关资源

Java 7 引入,自动关闭实现了 AutoCloseable 接口的资源:

// ❌ 老式写法:手动关,啰嗦还容易漏

FileInputStream fis = null;

try {

fis = new FileInputStream("test.txt");

// 读写...

} catch (IOException e) {

e.printStackTrace();

} finally {

if (fis != null) {

try {

fis.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

// ✅ try-with-resources:自动关闭,优雅

try (FileInputStream fis = new FileInputStream("test.txt");

BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {

String line;

while ((line = br.readLine()) != null) {

System.out.println(line);

}

} catch (IOException e) {

System.out.println("文件读取失败:" + e.getMessage());

}

好处

  • 自动调用 close(),不会漏关资源
  • 支持同时管理多个资源(分号分隔)
  • 关闭时的异常也会被正确处理

1.6 自定义异常

业务上需要特殊异常类型时,自己定义:

// 自定义受检异常

class BusinessException extends Exception {

public BusinessException(String message) {

super(message);

}

public BusinessException(String message, Throwable cause) {

super(message, cause);

}

}

// 自定义运行时异常(更常用)

class InsufficientBalanceException extends RuntimeException {

private final double balance;

private final double amount;

public InsufficientBalanceException(double balance, double amount) {

super("余额不足:余额 " + balance + ",需要 " + amount);

this.balance = balance;

this.amount = amount;

}

public double getBalance() { return balance; }

public double getAmount() { return amount; }

}

// 使用

class BankAccount {

private double balance;

void withdraw(double amount) {

if (amount > balance) {

throw new InsufficientBalanceException(balance, amount);

}

balance -= amount;

}

}

1.7 异常处理最佳实践

表格

✅ 推荐 ❌ 不推荐
具体异常具体捕获,不笼统 catch Exception 写空 catch 吞掉异常
捕获后打印或记录异常信息 只 catch 不处理(生吞异常)
异常信息要有意义,方便排查 异常堆栈信息打一半
资源用 try-with-resources finally 里写 return
自定义异常携带业务信息 所有异常都抛 RuntimeException
异常链用 cause 传递原始异常 捕获后重新抛出但丢失原始信息

⚠️ 最常见的坏味道:生吞异常

java

9

1

2

3

4

5

6

try {

// ...

} catch (Exception e) {

// 啥也不做!出了问题根本不知道

}

这是在给自己和同事挖坑,线上出问题排查到秃头。

第二部分:常用工具类

2.1 字符串三剑客:String、StringBuilder、StringBuffer

String——不可变字符串

String s1 = "Hello";

String s2 = new String("Hello");

// 常用方法

s1.length(); // 5

s1.charAt(0); // 'H'

s1.substring(1, 3); // "el"(含头不含尾)

s1.indexOf("l"); // 2

s1.lastIndexOf("l"); // 3

s1.equals(s2); // true(比较内容)

s1.equalsIgnoreCase(s2); // true

s1.toUpperCase(); // "HELLO"

s1.toLowerCase(); // "hello"

s1.trim(); // 去除首尾空白

s1.split(","); // 按逗号分割成数组

s1.replace("l", "x"); // "Hexxo"

s1.startsWith("He"); // true

s1.endsWith("lo"); // true

s1.contains("ell"); // true

String.join("-", "a", "b", "c"); // "a-b-c"(静态方法)

💡 字符串比较永远用 equals(),别用 ==!

java

9

1

2

3

4

5

String a = "hello";

String b = new String("hello");

a == b; // false(地址不同)

a.equals(b); // true(内容相同)

StringBuilder——可变字符串(非线程安全)

StringBuilder sb = new StringBuilder("Hello");

sb.append(" World"); // "Hello World"(追加)

sb.insert(5, ","); // "Hello, World"(插入)

sb.delete(5, 6); // "Hello World"(删除)

sb.replace(0, 5, "Hi"); // "Hi World"(替换)

sb.reverse(); // "dlroW iH"(反转)

sb.length(); // 长度

sb.toString(); // 转回 String

StringBuffer——可变字符串(线程安全,用得少)

方法和 StringBuilder 一模一样,只是每个方法都加了 synchronized,线程安全但性能低。单线程环境一律用 StringBuilder。

表格

特性 String StringBuilder StringBuffer
可变性 ❌ 不可变 ✅ 可变 ✅ 可变
线程安全 ✅ 安全 ❌ 不安全 ✅ 安全
性能 差(每次新建对象) 中(同步开销)
适用场景 少量操作、字符串常量 单线程大量拼接 多线程

2.2 Math——数学工具类

Math.abs(-10); // 10(绝对值)

Math.max(3, 7); // 7(最大值)

Math.min(3, 7); // 3(最小值)

Math.round(3.6); // 4(四舍五入)

Math.ceil(3.1); // 4.0(向上取整)

Math.floor(3.9); // 3.0(向下取整)

Math.pow(2, 3); // 8.0(幂运算)

Math.sqrt(16); // 4.0(平方根)

Math.random(); // 0.0 ~ 1.0 之间的随机数

Math.PI; // π

Math.E; // 自然常数

// [0, n) 范围的随机整数

int randomNum = (int) (Math.random() * 100); // 0 ~ 99

💡 需要更强大的随机数?用 java.util.RandomThreadLocalRandom

2.3 Arrays——数组工具类

int[] arr = {5, 2, 8, 1, 9};

// 排序

Arrays.sort(arr); // [1, 2, 5, 8, 9]

// 二分查找(必须先排序)

int index = Arrays.binarySearch(arr, 5); // 2(找到,返回索引)

int index2 = Arrays.binarySearch(arr, 3); // -2(没找到,返回插入点-1)

// 填充

int[] newArr = new int[5];

Arrays.fill(newArr, 10); // [10, 10, 10, 10, 10]

// 复制

int[] copy = Arrays.copyOf(arr, 3); // [1, 2, 5](前3个)

int[] copyRange = Arrays.copyOfRange(arr, 1, 4); // [2, 5, 8]

// 比较内容

Arrays.equals(arr, newArr); // false

// 转字符串(打印数组必备!)

System.out.println(Arrays.toString(arr)); // [1, 2, 5, 8, 9]

// 二维数组打印

int[][] matrix = {{1,2}, {3,4}};

System.out.println(Arrays.deepToString(matrix)); // [[1, 2], [3, 4]]

⚠️ 别直接 System.out.println(arr),会打印地址哈希值。必须用 Arrays.toString()

2.4 Collections——集合工具类

List<Integer> list = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9));

// 排序

Collections.sort(list); // [1, 2, 5, 8, 9]

Collections.sort(list, Collections.reverseOrder()); // 降序

// 反序、打乱

Collections.reverse(list); // 反转

Collections.shuffle(list); // 随机打乱

// 最大最小

Collections.max(list); // 9

Collections.min(list); // 1

// 填充、替换

Collections.fill(list, 0); // 全部替换成 0

Collections.replaceAll(list, 5, 50); // 把所有 5 换成 50

// 频率

int count = Collections.frequency(list, 2); // 元素 2 出现几次

// 不可变集合(返回的集合不能修改,安全)

List<String> unmodifiable = Collections.unmodifiableList(list);

2.5 Objects——空指针安全工具类

Java 7 引入,专门处理 null 的利器,强烈推荐用起来

// 安全比较(null 不会抛 NPE)

Objects.equals("a", "a"); // true

Objects.equals(null, "a"); // false(不会空指针!)

Objects.equals(null, null); // true

// 哈希码

Objects.hash("a", "b", "c"); // 多个对象的哈希

// toString 安全版

Objects.toString(null); // "null"

Objects.toString(null, "默认"); // "默认"

// 非空判断

Objects.isNull(null); // true

Objects.nonNull("abc"); // true

// 要求非空(抛异常带提示信息)

Objects.requireNonNull(obj, "对象不能为空");

Objects.requireNonNullElse(obj, "默认值"); // 为空返回默认值

💡 重写 equals 和 hashCode 时,用 Objects 工具类写出来既优雅又安全。

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

User user = (User) o;

return age == user.age

&& Objects.equals(name, user.name)

&& Objects.equals(email, user.email);

}

@Override

public int hashCode() {

return Objects.hash(name, age, email);

}

2.6 Optional——优雅处理空值

Java 8 引入,用容器思维替代 null 判断:

// 创建

Optional<String> opt1 = Optional.of("hello"); // 非空值

Optional<String> opt2 = Optional.ofNullable(null); // 可能为 null

Optional<String> opt3 = Optional.empty(); // 空容器

// 是否有值

opt1.isPresent(); // true

opt1.isEmpty(); // false

// 有值就消费

opt1.ifPresent(s -> System.out.println(s)); // 有值就打印

// 取值

opt1.get(); // 直接取(空会报错,少用)

opt2.orElse("默认值"); // 空就返回默认

opt2.orElseGet(() -> "懒加载默认"); // 空就用 Supplier 生成

opt2.orElseThrow(() -> new RuntimeException("值不能为空")); // 空就抛异常

// 转换(流式编程风格)

String result = opt1

.map(String::toUpperCase)

.filter(s -> s.length() > 3)

.orElse("不满足条件");

// result = "HELLO"

⚠️ Optional 别滥用

  • ✅ 方法返回值可能为空时用 Optional 包装
  • ❌ 不要用 Optional 作为类的字段
  • ❌ 不要用 Optional 作为方法参数
  • ❌ 别 Optional.ofNullable(x).isPresent()x != null 没啥区别

2.7 System——系统级工具

// 获取当前时间戳(毫秒)

long startTime = System.currentTimeMillis();

// ... 执行代码 ...

long endTime = System.currentTimeMillis();

System.out.println("耗时:" + (endTime - startTime) + "ms");

// 更精确的纳秒时间(用于基准测试,相对时间)

long nanoStart = System.nanoTime();

// 数组拷贝(比循环快很多,底层 native)

int[] src = {1, 2, 3, 4, 5};

int[] dest = new int[5];

System.arraycopy(src, 0, dest, 0, src.length);

// 获取系统属性

System.getProperty("java.version"); // Java 版本

System.getProperty("os.name"); // 操作系统

System.getProperty("user.home"); // 用户主目录

System.getProperty("file.encoding"); // 文件编码

// 退出 JVM(0 正常退出,非 0 异常退出)

// System.exit(0); // 少用,正常让程序自然结束

2.8 Java 8 时间 API(重点!)

旧的 Date/Calendar 又难用又有线程安全问题,Java 8 直接重做了一整套。新项目一律用新 API。

表格

旧 API 新 API 说明
Date LocalDate / LocalTime / LocalDateTime 日期/时间/日期时间
Calendar - 废弃不用
SimpleDateFormat DateTimeFormatter 格式化(线程安全)
TimeZone ZoneId / ZonedDateTime 时区
- Duration / Period 时间间隔
- Instant 时间戳

LocalDate / LocalTime / LocalDateTime

// 当前日期时间

LocalDate today = LocalDate.now(); // 2026-06-09

LocalTime now = LocalTime.now(); // 21:30:45.123

LocalDateTime dt = LocalDateTime.now(); // 2026-06-09T21:30:45.123

// 指定日期

LocalDate birthday = LocalDate.of(2000, 1, 1);

LocalTime time = LocalTime.of(14, 30, 0);

LocalDateTime meeting = LocalDateTime.of(2026, 6, 15, 14, 0);

// 获取字段

today.getYear(); // 2026

today.getMonth(); // JUNE(枚举)

today.getMonthValue(); // 6

today.getDayOfMonth(); // 9

today.getDayOfWeek(); // TUESDAY

today.lengthOfMonth(); // 30(这个月有几天)

// 修改(返回新对象,不可变)

today.plusDays(7); // 加7天

today.minusMonths(1); // 减1个月

today.withYear(2025); // 改年份

格式化与解析

// DateTimeFormatter 是线程安全的!

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 格式化(日期 → 字符串)

String str = dt.format(fmt); // "2026-06-09 21:30:45"

// 解析(字符串 → 日期)

LocalDateTime parsed = LocalDateTime.parse("2026-06-09 21:30:45", fmt);

时间间隔

// Period:日期间隔(年月日)

LocalDate start = LocalDate.of(2020, 1, 1);

LocalDate end = LocalDate.of(2026, 6, 9);

Period period = Period.between(start, end);

period.getYears(); // 6

period.getMonths(); // 5

period.getDays(); // 8

// Duration:时间间隔(秒/纳秒,更精确)

LocalTime t1 = LocalTime.of(10, 0);

LocalTime t2 = LocalTime.of(12, 30);

Duration d = Duration.between(t1, t2);

d.toHours(); // 2

d.toMinutes(); // 150

d.getSeconds(); // 9000

Instant 时间戳

Instant now = Instant.now(); // 当前时间戳(UTC)

now.toEpochMilli(); // 毫秒时间戳

now.getEpochSecond(); // 秒级时间戳

// 与 Date 互转(兼容旧代码)

Date date = Date.from(now);

Instant ins = date.toInstant();

三、实战练习

练习1:用户注册 —— 自定义异常 + 参数校验

class UserRegisterException extends RuntimeException {

public UserRegisterException(String msg) { super(msg); }

}

class UserService {

public void register(String username, String password, String email) {

// 非空校验

if (Objects.isNull(username) || username.isBlank()) {

throw new UserRegisterException("用户名不能为空");

}

if (Objects.isNull(password) || password.length() < 6) {

throw new UserRegisterException("密码长度不能少于6位");

}

if (Objects.isNull(email) || !email.contains("@")) {

throw new UserRegisterException("邮箱格式不正确");

}

// 注册逻辑...

System.out.println("用户 " + username + " 注册成功");

}

}

// 调用方处理

UserService service = new UserService();

try {

service.register("张三", "123", "zhangsan@example.com");

} catch (UserRegisterException e) {

System.out.println("注册失败:" + e.getMessage());

// 注册失败:密码长度不能少于6位

}

练习2:数组工具综合运用

public class ArrayExercise {

public static void main(String[] args) {

int[] scores = {78, 92, 65, 85, 99, 72, 88};

// 1. 排序

Arrays.sort(scores);

System.out.println("排序后:" + Arrays.toString(scores));

// 2. 找最高分最低分

int max = scores[scores.length - 1];

int min = scores[0];

System.out.println("最高分:" + max + ",最低分:" + min);

// 3. 计算平均分

int sum = 0;

for (int s : scores) sum += s;

double avg = (double) sum / scores.length;

System.out.printf("平均分:%.1f\n", avg);

// 4. 查找 85 分

int idx = Arrays.binarySearch(scores, 85);

System.out.println("85分的位置:" + (idx >= 0 ? idx : "不存在"));

// 5. 前三名

int[] top3 = Arrays.copyOfRange(scores, scores.length - 3, scores.length);

System.out.println("前三名:" + Arrays.toString(top3));

}

}

练习3:日期工具 —— 计算两个日期相差几天

public class DateUtils {

// 计算两个日期之间相差的天数

public static long daysBetween(String startStr, String endStr) {

LocalDate start = LocalDate.parse(startStr);

LocalDate end = LocalDate.parse(endStr);

return ChronoUnit.DAYS.between(start, end);

}

// 判断是不是生日(月日相同)

public static boolean isBirthday(LocalDate birthday) {

LocalDate today = LocalDate.now();

return birthday.getMonth() == today.getMonth()

&& birthday.getDayOfMonth() == today.getDayOfMonth();

}

// 获取今天是今年的第几天

public static int dayOfYear() {

return LocalDate.now().getDayOfYear();

}

public static void main(String[] args) {

long days = daysBetween("2026-01-01", "2026-06-09");

System.out.println("距离元旦过去 " + days + " 天"); // 159 天

System.out.println("今天是今年第 " + dayOfYear() + " 天");

}

}

四、总结速查表

异常处理

表格

内容 要点
异常体系 Error(严重)、Exception(可处理),RuntimeException 是非受检
处理方式 try-catch-finally、try-with-resources、throws
finally 必执行(除非 System.exit),别在里面写 return
try-with-resources 自动关资源,实现 AutoCloseable 就行
自定义异常 继承 RuntimeException 更灵活,可以带业务数据
注意 别生吞异常、别用 == 比字符串、别 catch Exception 了事

常用工具类

表格

工具类 用途 记忆点
String 不可变字符串 字符串拼接用 StringBuilder
StringBuilder 可变字符串 非线程安全,性能好
Math 数学运算 abs/max/min/round/pow/random
Arrays 数组操作 sort/binarySearch/copyOf/toString
Collections 集合操作 sort/max/min/shuffle/unmodifiableList
Objects null 安全工具 equals/requireNonNull/hash
Optional 空值容器 方法返回值用,别当字段用
System 系统级 currentTimeMillis/arraycopy
LocalDate/LocalDateTime 新日期 API 不可变、线程安全,替代 Date
DateTimeFormatter 日期格式化 线程安全,替代 SimpleDateFormat
Duration/Period 时间间隔 Duration 精确,Period 用年月日

工具类是开发者的"瑞士军刀",用熟了效率翻倍;异常处理是代码的"安全气囊",处理得好才能在生产环境跑得稳。这两块东西不复杂,但细节不少,多用几次就记住了。

更多推荐