java 常用工具类与异常处理
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.Random或ThreadLocalRandom。
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 用年月日 |
工具类是开发者的"瑞士军刀",用熟了效率翻倍;异常处理是代码的"安全气囊",处理得好才能在生产环境跑得稳。这两块东西不复杂,但细节不少,多用几次就记住了。
更多推荐
所有评论(0)