一、Java运行时异常体系结构
1. Java异常类层次结构
php
Throwable
├── Error (严重错误,通常不需要捕获)
│ ├── VirtualMachineError
│ │ ├── OutOfMemoryError
│ │ ├── StackOverflowError
│ │ └── InternalError
│ ├── LinkageError
│ │ ├── NoClassDefFoundError
│ │ ├── ClassFormatError
│ │ └── IncompatibleClassChangeError
│ └── AssertionError
│
└── Exception
├── RuntimeException (运行时异常,无需强制捕获)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── IllegalArgumentException
│ ├── IllegalStateException
│ ├── ConcurrentModificationException
│ ├── UnsupportedOperationException
│ ├── ArithmeticException
│ └── NumberFormatException
│
└── 其他检查型异常 (必须处理)
二、常见运行时异常详解
1. 空指针异常 (NullPointerException, NPE)
错误示例:
ini
String str = null;
int length = str.length(); // NullPointerException
String[] array = null;
String first = array[0]; // NullPointerException
Map<String, String> map = null;
String value = map.get("key"); // NullPointerException
解决方案:
java
// 1. 明确检查null
String str = getString();
if (str != null) {
int length = str.length();
}
// 2. 使用Objects.requireNonNull (Java 7+)
import java.util.Objects;
String input = getInput();
String validated = Objects.requireNonNull(input, "Input cannot be null");
// 3. 使用Optional (Java 8+)
import java.util.Optional;
Optional<String> optionalStr = Optional.ofNullable(getString());
int length = optionalStr.map(String::length).orElse(0);
// 4. 使用默认值
String str = getString();
int length = (str != null) ? str.length() : 0;
// 5. 使用第三方库的null安全方法
// Apache Commons
import org.apache.commons.lang3.StringUtils;
if (StringUtils.isNotEmpty(str)) {
int length = str.length();
}
// 6. 返回空集合而不是null
public List<String> getItems() {
List<String> items = fetchItems();
return items != null ? items : Collections.emptyList();
}
// 7. 防御性编程
public void process(@NonNull String input) { // 使用注解
// 编译器或静态分析工具会检查
}
使用Lombok的@NonNull注解:
less
import lombok.NonNull;
public class UserService {
public void createUser(@NonNull String username, @NonNull String email) {
// 参数自动进行null检查
// Lombok会在编译时生成检查代码
}
}
2. 数组索引越界异常 (ArrayIndexOutOfBoundsException)
错误示例:
ini
int[] arr = {1, 2, 3};
int value = arr[3]; // 有效索引: 0-2,访问3会抛出异常
String[] names = new String[0];
String first = names[0]; // 数组为空
解决方案:
ini
// 1. 检查索引范围
int[] arr = {1, 2, 3};
int index = 3;
if (index >= 0 && index < arr.length) {
int value = arr[index];
} else {
// 处理越界情况
throw new IllegalArgumentException("索引越界: " + index);
}
// 2. 使用for循环而不是手动索引
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]); // 安全
}
// 3. 使用增强for循环
for (int num : arr) {
System.out.println(num); // 不会越界
}
// 4. 处理空数组
String[] names = getNames();
if (names != null && names.length > 0) {
String first = names[0];
}
// 5. 使用List代替数组
List<Integer> list = Arrays.asList(1, 2, 3);
if (index >= 0 && index < list.size()) {
int value = list.get(index);
}
3. 类型转换异常 (ClassCastException)
错误示例:
ini
Object obj = "Hello";
Integer num = (Integer) obj; // ClassCastException: String cannot be cast to Integer
List rawList = new ArrayList();
rawList.add("String");
rawList.add(123); // 混用类型
for (Object item : rawList) {
String str = (String) item; // 对123会抛出异常
}
解决方案:
ini
// 1. 使用instanceof检查
Object obj = getObject();
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
} else if (obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.println(num + 1);
}
// 2. 使用泛型避免类型转换
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// 不需要类型转换
for (String str : stringList) {
System.out.println(str.length());
}
// 3. 使用模式匹配 (Java 16+)
Object obj = getObject();
if (obj instanceof String str) { // 自动转换
System.out.println(str.length());
} else if (obj instanceof Integer num) {
System.out.println(num + 1);
}
// 4. 使用Class.cast()方法
Class<?> targetType = String.class;
Object obj = "Hello";
if (targetType.isInstance(obj)) {
String str = targetType.cast(obj);
}
// 5. 安全的转换工具方法
public static <T> T safeCast(Object obj, Class<T> clazz) {
if (clazz.isInstance(obj)) {
return clazz.cast(obj);
}
return null;
}
String str = safeCast(obj, String.class);
if (str != null) {
// 安全使用
}
4. 并发修改异常 (ConcurrentModificationException)
错误示例:
arduino
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) { // 使用迭代器
if ("B".equals(item)) {
list.remove(item); // ConcurrentModificationException
}
}
解决方案:
ini
// 1. 使用迭代器的remove方法
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("B".equals(item)) {
iterator.remove(); // 安全
}
}
// 2. 使用Java 8+的removeIf方法
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
list.removeIf(item -> "B".equals(item)); // 线程安全
// 3. 创建副本进行迭代
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> copy = new ArrayList<>(list);
for (String item : copy) {
if ("B".equals(item)) {
list.remove(item); // 安全,因为迭代的是副本
}
}
// 4. 使用for循环倒序遍历删除
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (int i = list.size() - 1; i >= 0; i--) {
if ("B".equals(list.get(i))) {
list.remove(i);
}
}
// 5. 使用并发集合
import java.util.concurrent.CopyOnWriteArrayList;
List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("A", "B", "C"));
for (String item : list) { // 安全
if ("B".equals(item)) {
list.remove(item);
}
}
5. 非法参数异常 (IllegalArgumentException)
错误示例:
java
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("无效的年龄: " + age);
}
this.age = age;
}
// BigDecimal的除法
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("0");
BigDecimal result = dividend.divide(divisor); // 抛出ArithmeticException
解决方案:
java
// 1. 参数验证
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数: " + age);
}
if (age > 150) {
throw new IllegalArgumentException("年龄不合理: " + age);
}
this.age = age;
}
// 2. 使用验证库
// 使用Apache Commons Validator
import org.apache.commons.validator.routines.IntegerValidator;
IntegerValidator validator = IntegerValidator.getInstance();
if (!validator.isInRange(age, 0, 150)) {
throw new IllegalArgumentException("无效的年龄: " + age);
}
// 3. 使用断言
public void setAge(int age) {
assert age >= 0 : "年龄不能为负数";
assert age <= 150 : "年龄不合理";
this.age = age;
}
// 使用 -ea 启用断言
// 4. 安全的BigDecimal操作
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("0");
if (divisor.compareTo(BigDecimal.ZERO) == 0) {
throw new IllegalArgumentException("除数不能为零");
}
BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP); // 指定精度
// 5. 使用Objects.requireNonNull检查null参数
public void process(String input) {
Objects.requireNonNull(input, "输入参数不能为null");
// 处理逻辑
}
6. 算术异常 (ArithmeticException)
错误示例:
ini
int a = 10;
int b = 0;
int result = a / b; // ArithmeticException: / by zero
int min = Integer.MIN_VALUE;
int negated = -min; // 溢出,实际还是MIN_VALUE
int result = Math.abs(min); // 还是MIN_VALUE
int divided = min / -1; // 溢出
解决方案:
ini
// 1. 检查除数
int a = 10;
int b = 0;
if (b != 0) {
int result = a / b;
} else {
throw new ArithmeticException("除数不能为零");
}
// 2. 使用BigInteger/BigDecimal
BigDecimal dividend = new BigDecimal("10");
BigDecimal divisor = new BigDecimal("3");
BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP);
// 3. 处理整数溢出
public int safeAdd(int a, int b) {
long result = (long) a + (long) b;
if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
throw new ArithmeticException("整数溢出");
}
return (int) result;
}
// 4. 使用Math.*Exact方法 (Java 8+)
try {
int result = Math.addExact(a, b);
int product = Math.multiplyExact(a, b);
int negated = Math.negateExact(a);
} catch (ArithmeticException e) {
// 处理溢出
}
// 5. 使用无符号运算
int a = Integer.MIN_VALUE;
int b = Integer.divideUnsigned(a, 2); // 无符号除法
long unsignedValue = Integer.toUnsignedLong(a);
7. 数字格式异常 (NumberFormatException)
错误示例:
ini
String str = "123abc";
int num = Integer.parseInt(str); // NumberFormatException
String empty = "";
int value = Integer.parseInt(empty); // NumberFormatException
String decimal = "12.34";
int intValue = Integer.parseInt(decimal); // NumberFormatException
解决方案:
ini
// 1. 使用try-catch
String str = "123abc";
try {
int num = Integer.parseInt(str);
} catch (NumberFormatException e) {
System.err.println("无效的数字格式: " + str);
// 使用默认值
int num = 0;
}
// 2. 使用Scanner
String str = "123abc";
Scanner scanner = new Scanner(str);
if (scanner.hasNextInt()) {
int num = scanner.nextInt();
} else {
// 不是有效整数
}
// 3. 使用正则表达式验证
String str = "123abc";
if (str.matches("-?\d+")) { // 匹配整数
int num = Integer.parseInt(str);
}
// 4. 使用Apache Commons Lang
import org.apache.commons.lang3.math.NumberUtils;
String str = "123abc";
if (NumberUtils.isCreatable(str)) {
int num = NumberUtils.createInteger(str);
}
// 5. 使用Java 8 Optional
import java.util.Optional;
String str = "123abc";
Optional<Integer> num = parseInteger(str);
num.ifPresentOrElse(
n -> System.out.println("数字: " + n),
() -> System.out.println("无效数字")
);
private Optional<Integer> parseInteger(String str) {
try {
return Optional.of(Integer.parseInt(str));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
8. 不支持的操作异常 (UnsupportedOperationException)
错误示例:
ini
List<String> fixedList = Arrays.asList("A", "B", "C");
fixedList.add("D"); // UnsupportedOperationException
List<String> unmodifiable = Collections.unmodifiableList(new ArrayList<>());
unmodifiable.add("X"); // UnsupportedOperationException
解决方案:
csharp
// 1. 创建可变副本
List<String> fixedList = Arrays.asList("A", "B", "C");
List<String> mutableList = new ArrayList<>(fixedList);
mutableList.add("D"); // 可以修改
// 2. 检查集合是否支持操作
List<String> list = getList();
if (list instanceof ArrayList) { // 通常是可变的
list.add("new item");
} else if (list.getClass().getSimpleName().contains("Unmodifiable")) {
// 创建可变副本
list = new ArrayList<>(list);
list.add("new item");
}
// 3. 使用正确的集合类型
// 如果需要可变列表
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
list.add("D"); // 可以
// 如果需要不可变列表(Java 9+)
List<String> immutable = List.of("A", "B", "C"); // 完全不可变
// 4. 自定义集合
class CustomList<E> extends AbstractList<E> {
private final List<E> delegate = new ArrayList<>();
@Override
public E get(int index) {
return delegate.get(index);
}
@Override
public int size() {
return delegate.size();
}
// 实现add、remove等需要的方法
@Override
public boolean add(E e) {
return delegate.add(e);
}
}
9. 非法状态异常 (IllegalStateException)
错误示例:
arduino
public class Connection {
private boolean open = false;
public void sendData(String data) {
if (!open) {
throw new IllegalStateException("连接未打开");
}
// 发送数据
}
}
// Iterator使用
List<String> list = Arrays.asList("A", "B");
Iterator<String> it = list.iterator();
it.next();
it.remove(); // 可以
it.remove(); // IllegalStateException: 没有调用next
解决方案:
typescript
// 1. 状态验证
public class Connection implements AutoCloseable {
private boolean open = false;
public void open() {
if (open) {
throw new IllegalStateException("连接已打开");
}
open = true;
}
public void sendData(String data) {
ensureOpen();
// 发送数据
}
private void ensureOpen() {
if (!open) {
throw new IllegalStateException("连接未打开");
}
}
@Override
public void close() {
open = false;
}
}
// 2. 正确的Iterator使用
List<String> list = new ArrayList<>(Arrays.asList("A", "B"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (shouldRemove(item)) {
it.remove(); // 正确:每个remove前都有next
}
}
// 3. 使用状态模式
interface ConnectionState {
void sendData(String data);
void open();
void close();
}
class ClosedState implements ConnectionState {
private final Connection connection;
ClosedState(Connection connection) {
this.connection = connection;
}
@Override
public void sendData(String data) {
throw new IllegalStateException("连接已关闭");
}
@Override
public void open() {
connection.setState(new OpenState(connection));
}
@Override
public void close() {
// 已经是关闭状态
}
}
三、严重错误 (Errors)
1. 内存溢出错误 (OutOfMemoryError)
原因:
- 堆内存不足
- 永久代/元空间不足
- 栈内存不足
- 创建过多线程
解决方案:
java
// 1. 增加JVM堆内存
// java -Xms512m -Xmx2048m -XX:MaxMetaspaceSize=256m MyApp
// 2. 分析内存泄漏
// 使用MAT (Memory Analyzer Tool) 或 VisualVM
// 3. 使用软引用/弱引用缓存
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
// 软引用:内存不足时会被回收
SoftReference<BigObject> softRef = new SoftReference<>(new BigObject());
// 弱引用:GC时立即回收
WeakReference<BigObject> weakRef = new WeakReference<>(new BigObject());
// 4. 及时释放资源
try (InputStream is = new FileInputStream("largefile.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
// 处理文件
} // 自动关闭
// 5. 使用对象池
class ObjectPool<T> {
private final Queue<T> pool = new ConcurrentLinkedQueue<>();
private final Supplier<T> creator;
public ObjectPool(Supplier<T> creator, int size) {
this.creator = creator;
for (int i = 0; i < size; i++) {
pool.offer(creator.get());
}
}
public T borrow() {
T obj = pool.poll();
return obj != null ? obj : creator.get();
}
public void returnObject(T obj) {
pool.offer(obj);
}
}
JVM参数调优:
ruby
# 堆内存设置
-Xms512m # 初始堆大小
-Xmx2048m # 最大堆大小
-Xmn256m # 新生代大小
-XX:NewRatio=3 # 新生代:老年代 = 1:3
# 元空间设置
-XX:MaxMetaspaceSize=256m
-XX:MetaspaceSize=128m
# 栈大小设置
-Xss1m # 每个线程栈大小
# GC设置
-XX:+UseG1GC # 使用G1垃圾收集器
-XX:+UseConcMarkSweepGC # 使用CMS收集器
-XX:+PrintGCDetails # 打印GC详情
-XX:+HeapDumpOnOutOfMemoryError # OOM时生成堆转储
2. 栈溢出错误 (StackOverflowError)
错误示例:
csharp
// 递归无终止条件
public int infiniteRecursion(int n) {
return infiniteRecursion(n + 1); // StackOverflowError
}
// 相互递归
public void methodA() {
methodB();
}
public void methodB() {
methodA(); // 相互调用导致栈溢出
}
解决方案:
arduino
// 1. 添加递归终止条件
public int factorial(int n) {
if (n <= 1) { // 终止条件
return 1;
}
return n * factorial(n - 1);
}
// 2. 使用迭代代替递归
public int factorialIterative(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// 3. 使用尾递归优化
public int factorialTailRecursive(int n, int accumulator) {
if (n <= 1) {
return accumulator;
}
return factorialTailRecursive(n - 1, n * accumulator);
}
// 注意:Java不直接支持尾调用优化,但有些JVM会尝试
// 4. 增加栈大小
// java -Xss2m MyApp # 设置栈大小为2MB
// 5. 使用循环和栈数据结构
public void depthFirstSearch(Node root) {
if (root == null) return;
Deque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
process(node);
for (Node child : node.getChildren()) {
stack.push(child);
}
}
}
3. 类定义未找到错误 (NoClassDefFoundError)
原因:
- 类路径中缺少类文件
- 静态初始化失败
- 版本不兼容
解决方案:
typescript
// 1. 检查类路径
System.out.println("Classpath: " + System.getProperty("java.class.path"));
// 2. 使用正确的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
} catch (ClassNotFoundException e) {
// 处理类找不到的情况
}
// 3. 使用Maven/Gradle管理依赖
// pom.xml
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>mylibrary</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
// 4. 检查静态初始化块
public class ProblematicClass {
static {
// 如果这里抛出异常,会导致NoClassDefFoundError
if (System.currentTimeMillis() % 2 == 0) {
throw new RuntimeException("静态初始化失败");
}
}
}
// 解决方案:将初始化移到静态方法中
public class SafeClass {
private static volatile boolean initialized = false;
public static synchronized void init() {
if (!initialized) {
// 初始化代码
initialized = true;
}
}
}
四、并发编程运行时错误
1. 死锁 (Deadlock)
java
public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) { // 可能阻塞
// 临界区
}
}
}
public void method2() {
synchronized (lock2) {
synchronized (lock1) { // 可能阻塞
// 临界区
}
}
}
}
解决方案:
java
// 1. 按相同顺序获取锁
public class SafeLocking {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// 临界区
}
}
}
public void method2() {
synchronized (lock1) { // 相同顺序
synchronized (lock2) {
// 临界区
}
}
}
}
// 2. 使用ReentrantLock和tryLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public boolean tryMethod() {
boolean gotLock1 = false;
boolean gotLock2 = false;
try {
gotLock1 = lock1.tryLock(100, TimeUnit.MILLISECONDS);
gotLock2 = lock2.tryLock(100, TimeUnit.MILLISECONDS);
if (gotLock1 && gotLock2) {
// 成功获取两个锁
return true;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (gotLock1) lock1.unlock();
if (gotLock2) lock2.unlock();
}
return false;
}
}
// 3. 使用Lock.tryLock()和超时
public void methodWithTimeout() throws InterruptedException {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 成功获取两个锁
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
}
// 4. 使用死锁检测
// 在代码审查时检查锁的顺序
// 使用jstack或VisualVM检测运行时的死锁
使用jstack检测死锁:
shell
# 1. 查找Java进程ID
jps
# 2. 生成线程转储
jstack <pid> > thread_dump.txt
# 3. 搜索"deadlock"关键字
2. 线程安全问题
csharp
// 错误示例:非线程安全的单例
public class UnsafeSingleton {
private static UnsafeSingleton instance;
public static UnsafeSingleton getInstance() {
if (instance == null) { // 竞争条件
instance = new UnsafeSingleton();
}
return instance;
}
}
解决方案:
java
// 1. 同步方法
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
// 2. 双重检查锁定 (正确版本)
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
public static DoubleCheckedSingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) { // 第二次检查
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
// 3. 静态内部类 (推荐)
public class StaticHolderSingleton {
private StaticHolderSingleton() {}
private static class Holder {
static final StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
}
public static StaticHolderSingleton getInstance() {
return Holder.INSTANCE;
}
}
// 4. 枚举单例 (最佳实践)
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// ...
}
}
// 5. 使用java.util.concurrent类
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
public void increment() {
count.incrementAndGet(); // 线程安全
}
public void addToMap(String key, String value) {
map.put(key, value); // 线程安全
}
}
五、调试和诊断工具
1. JVM监控工具
ini
# 1. jps - 查看Java进程
jps -l
# 2. jstack - 线程转储
jstack <pid> > thread_dump.txt
# 3. jmap - 内存分析
jmap -heap <pid> # 堆信息
jmap -histo <pid> # 直方图
jmap -dump:format=b,file=heap.bin <pid> # 堆转储
# 4. jstat - 统计信息
jstat -gc <pid> 1000 10 # 每1秒收集一次GC信息,共10次
# 5. jcmd - 多功能命令
jcmd <pid> VM.version
jcmd <pid> GC.class_histogram
jcmd <pid> Thread.print
jcmd <pid> GC.heap_dump filename=heap.hprof
2. VisualVM使用
ini
// 启用JMX监控
// java -Dcom.sun.management.jmxremote \
// -Dcom.sun.management.jmxremote.port=9010 \
// -Dcom.sun.management.jmxremote.ssl=false \
// -Dcom.sun.management.jmxremote.authenticate=false \
// MyApp
3. Java Flight Recorder (JFR)
ini
# 启用JFR
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder MyApp
# 记录JFR
jcmd <pid> JFR.start name=myrecording duration=60s filename=recording.jfr
jcmd <pid> JFR.dump name=myrecording filename=recording.jfr
jcmd <pid> JFR.stop name=myrecording
六、防御性编程和最佳实践
1. 输入验证
typescript
public class InputValidator {
public static String validateUsername(String username) {
if (username == null) {
throw new IllegalArgumentException("用户名不能为null");
}
username = username.trim();
if (username.isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (username.length() < 3 || username.length() > 20) {
throw new IllegalArgumentException("用户名长度必须在3-20之间");
}
if (!username.matches("^[a-zA-Z0-9_]+$")) {
throw new IllegalArgumentException("用户名只能包含字母、数字和下划线");
}
return username;
}
public static int validateAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在0-150之间");
}
return age;
}
}
2. 使用不可变对象
arduino
// 不可变类
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}
public String getName() { return name; }
public int getAge() { return age; }
public List<String> getHobbies() { return hobbies; } // 不可修改
}
3. 资源管理
java
// 使用try-with-resources
public void processFile(String filename) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filename));
BufferedWriter writer = new BufferedWriter(new FileWriter(filename + ".out"))) {
String line;
while ((line = reader.readLine()) != null) {
String processed = processLine(line);
writer.write(processed);
writer.newLine();
}
} // 自动关闭资源
}
// 使用AutoCloseable
class DatabaseConnection implements AutoCloseable {
private Connection connection;
public DatabaseConnection(String url) throws SQLException {
this.connection = DriverManager.getConnection(url);
}
public void execute(String sql) throws SQLException {
try (Statement stmt = connection.createStatement()) {
stmt.execute(sql);
}
}
@Override
public void close() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}
七、异常处理最佳实践
1. 正确的异常处理
typescript
public class ExceptionHandling {
// 1. 捕获具体异常
public void processFile(String filename) {
try {
readFile(filename);
} catch (FileNotFoundException e) {
// 文件不存在
logger.error("文件不存在: {}", filename, e);
throw new BusinessException("文件不存在", e);
} catch (IOException e) {
// IO错误
logger.error("读取文件失败: {}", filename, e);
throw new BusinessException("读取失败", e);
} catch (Exception e) {
// 其他异常
logger.error("处理文件时发生未知错误: {}", filename, e);
throw new BusinessException("处理失败", e);
}
}
// 2. 使用finally清理资源
public void processResource() {
Resource resource = null;
try {
resource = acquireResource();
useResource(resource);
} catch (ResourceException e) {
handleException(e);
} finally {
if (resource != null) {
try {
resource.close();
} catch (Exception e) {
logger.warn("关闭资源时出错", e);
}
}
}
}
// 3. 不要吞掉异常
public void badPractice() {
try {
doSomething();
} catch (Exception e) {
// 错误:吞掉异常,没有记录或重新抛出
}
}
// 4. 使用自定义异常
public class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() { return errorCode; }
}
}
八、常见运行时错误速查表
| 错误类型 | 常见原因 | 预防措施 | 工具检测 |
|---|---|---|---|
| NullPointerException | 解引用null | 使用Optional,检查null | SpotBugs, NullAway |
| ArrayIndexOutOfBoundsException | 数组越界 | 检查索引范围 | 代码审查,测试 |
| ClassCastException | 类型转换错误 | 使用instanceof检查 | 使用泛型 |
| ConcurrentModificationException | 并发修改集合 | 使用迭代器remove,CopyOnWriteArrayList | 测试 |
| IllegalArgumentException | 无效参数 | 参数验证 | 单元测试 |
| IllegalStateException | 状态不正确 | 状态检查 | 状态机验证 |
| OutOfMemoryError | 内存不足 | 内存管理,使用缓存 | VisualVM, MAT |
| StackOverflowError | 递归过深 | 迭代代替递归 | 栈大小分析 |
| NoClassDefFoundError | 类路径问题 | 依赖管理 | Maven/Gradle |
| Deadlock | 锁顺序问题 | 固定锁顺序 | jstack, VisualVM |
| NumberFormatException | 格式错误 | 验证输入,使用try-catch | 输入验证 |
| ArithmeticException | 除以零等 | 检查除数 | 数学库函数 |
九、工具和库推荐
1. 静态分析工具
xml
# SpotBugs (原FindBugs)
mvn spotbugs:check
# Checkstyle
mvn checkstyle:check
# PMD
mvn pmd:check
# Error Prone
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.10.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
2. 测试框架
typescript
// JUnit 5测试异常
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@Test
void testException() {
Exception exception = assertThrows(
IllegalArgumentException.class,
() -> validator.validateUsername(null)
);
assertEquals("用户名不能为null", exception.getMessage());
}
// 使用AssertJ
import static org.assertj.core.api.Assertions.*;
@Test
void testWithAssertJ() {
assertThatThrownBy(() -> validator.validateUsername(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("用户名不能为空");
}
3. 日志框架
java
// 使用SLF4J + Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void process() {
try {
doSomething();
} catch (Exception e) {
logger.error("处理失败: {}", e.getMessage(), e);
// 结构化日志
logger.error("处理失败: userId={}, action={}", userId, action, e);
}
}
}
记住:预防胜于治疗。良好的编码习惯、充分的测试、代码审查和静态分析可以避免大多数运行时错误。当错误发生时,清晰的日志记录和适当的异常处理可以帮助快速定位和解决问题。