前言 异常处理是Java编程中不可或缺的重要机制,它帮助我们处理程序运行过程中可能出现的各种错误情况。良好的异常处理不仅可以提高程序的健壮性和用户体验,还能帮助开发者快速定位和解决问题。理解异常的本质、分类和处理方式,是编写高质量Java程序的必备技能。本文将全面介绍Java异常处理的各个方面,帮助您掌握这一重要的编程概念。
异常基础概念 什么是异常 异常(Exception)是程序执行过程中发生的中断正常指令流的事件。当程序运行时遇到无法处理的情况时,就会产生异常。Java通过异常机制提供了一种结构化的错误处理方式。
异常的层次结构 graph TD
A[Throwable] --> B[Error]
A --> C[Exception]
B --> B1[OutOfMemoryError]
B --> B2[StackOverflowError]
B --> B3[NoClassDefFoundError]
C --> C1[RuntimeException 运行时异常]
C --> C2[非RuntimeException 编译时异常]
C1 --> C11[NullPointerException]
C1 --> C12[ArrayIndexOutOfBoundsException]
C1 --> C13[ClassCastException]
C1 --> C14[NumberFormatException]
C1 --> C15[IllegalArgumentException]
C2 --> C21[IOException]
C2 --> C22[ClassNotFoundException]
C2 --> C23[SQLException]
C2 --> C24[FileNotFoundException]
D[异常分类] --> D1[受检异常 Checked]
D --> D2[非受检异常 Unchecked]
D1 --> D11[编译时必须处理]
D1 --> D12[继承自Exception但非RuntimeException]
D2 --> D21[运行时异常]
D2 --> D22[Error类异常]
异常的特点
异常是对象 :所有异常都是Throwable类的子类实例
异常具有传播性 :未处理的异常会向上传播直到被捕获或程序终止
异常包含信息 :异常对象包含错误信息、堆栈跟踪等调试信息
异常可以被处理 :通过try-catch机制可以捕获和处理异常
异常的分类 Error类异常 Error类异常表示严重的系统级错误,通常无法恢复,程序应该终止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ErrorDemo { public static void main (String[] args) { recursiveMethod(); } public static void recursiveMethod () { recursiveMethod(); } public static void outOfMemoryExample () { List<int []> memoryLeak = new ArrayList <>(); while (true ) { memoryLeak.add(new int [1000000 ]); } } }
运行时异常(RuntimeException) 运行时异常是程序逻辑错误导致的异常,可以通过修改代码避免:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class RuntimeExceptionDemo { public static void main (String[] args) { String str = null ; try { int length = str.length(); } catch (NullPointerException e) { System.out.println("空指针异常:" + e.getMessage()); } int [] arr = {1 , 2 , 3 }; try { int value = arr[10 ]; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组越界异常:" + e.getMessage()); } try { int num = Integer.parseInt("abc" ); } catch (NumberFormatException e) { System.out.println("数字格式异常:" + e.getMessage()); } try { Object obj = "Hello" ; Integer num = (Integer) obj; } catch (ClassCastException e) { System.out.println("类型转换异常:" + e.getMessage()); } } }
编译时异常(Checked Exception) 编译时异常必须在编译时处理,否则无法通过编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.io.*;import java.sql.*;public class CheckedExceptionDemo { public void readFile (String filename) throws IOException { FileReader file = new FileReader (filename); BufferedReader reader = new BufferedReader (file); String line = reader.readLine(); reader.close(); } public void loadClass (String className) throws ClassNotFoundException { Class<?> clazz = Class.forName(className); System.out.println("加载类:" + clazz.getName()); } public void connectDatabase () throws SQLException { throw new SQLException ("数据库连接失败" ); } public static void main (String[] args) { CheckedExceptionDemo demo = new CheckedExceptionDemo (); try { demo.readFile("test.txt" ); demo.loadClass("com.example.MyClass" ); demo.connectDatabase(); } catch (IOException e) { System.out.println("IO异常:" + e.getMessage()); } catch (ClassNotFoundException e) { System.out.println("类未找到异常:" + e.getMessage()); } catch (SQLException e) { System.out.println("SQL异常:" + e.getMessage()); } } }
try-catch-finally语句 基本语法 graph TD
A[try块] --> B{是否发生异常?}
B -->|是| C[catch块]
B -->|否| D[finally块]
C --> D
D --> E[程序继续执行]
F[异常处理流程] --> F1[try中发生异常]
F1 --> F2[寻找匹配的catch块]
F2 --> F3[执行catch块代码]
F3 --> F4[执行finally块]
F4 --> F5[继续执行或重新抛出]
G[多catch块匹配规则] --> G1[从上到下匹配]
G --> G2[子类异常在前]
G --> G3[父类异常在后]
G --> G4[只执行第一个匹配的catch]
详细示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 import java.io.*;public class TryCatchFinallyDemo { public static void main (String[] args) { basicTryCatch(); multipleCatch(); tryCatchFinally(); tryWithResources(); } public static void basicTryCatch () { System.out.println("=== 基本try-catch示例 ===" ); try { int result = 10 / 0 ; System.out.println("结果:" + result); } catch (ArithmeticException e) { System.out.println("捕获到算术异常:" + e.getMessage()); e.printStackTrace(); } System.out.println("程序继续执行\n" ); } public static void multipleCatch () { System.out.println("=== 多catch块示例 ===" ); String[] arr = {"100" , "abc" , null }; for (int i = 0 ; i < arr.length; i++) { try { String str = arr[i]; int length = str.length(); int num = Integer.parseInt(str); System.out.println("解析成功:" + num); } catch (NullPointerException e) { System.out.println("空指针异常:字符串为null" ); } catch (NumberFormatException e) { System.out.println("数字格式异常:无法解析为数字" ); } catch (Exception e) { System.out.println("其他异常:" + e.getMessage()); } } System.out.println(); } public static void tryCatchFinally () { System.out.println("=== try-catch-finally示例 ===" ); FileInputStream fis = null ; try { fis = new FileInputStream ("nonexistent.txt" ); } catch (FileNotFoundException e) { System.out.println("文件未找到:" + e.getMessage()); } catch (IOException e) { System.out.println("IO异常:" + e.getMessage()); } finally { System.out.println("执行finally块:清理资源" ); if (fis != null ) { try { fis.close(); } catch (IOException e) { System.out.println("关闭文件时发生异常:" + e.getMessage()); } } } System.out.println(); } public static void tryWithResources () { System.out.println("=== try-with-resources示例 ===" ); try (FileInputStream fis = new FileInputStream ("test.txt" ); BufferedReader reader = new BufferedReader (new InputStreamReader (fis))) { String line = reader.readLine(); System.out.println("读取到:" + line); } catch (FileNotFoundException e) { System.out.println("文件未找到:" + e.getMessage()); } catch (IOException e) { System.out.println("IO异常:" + e.getMessage()); } System.out.println(); } }
throws和throw关键字 throws声明异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import java.io.*;public class ThrowsDemo { public void readFile (String filename) throws IOException, FileNotFoundException { if (filename == null ) { throw new IllegalArgumentException ("文件名不能为null" ); } FileReader file = new FileReader (filename); BufferedReader reader = new BufferedReader (file); try { String line = reader.readLine(); System.out.println("读取到:" + line); } finally { reader.close(); } } public void processFile (String filename) throws IOException { readFile(filename); } public void handleFile (String filename) { try { processFile(filename); } catch (IOException e) { System.out.println("处理文件时发生异常:" + e.getMessage()); } } public static void main (String[] args) { ThrowsDemo demo = new ThrowsDemo (); demo.handleFile("test.txt" ); } }
throw主动抛出异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class ThrowDemo { public void validateAge (int age) { if (age < 0 ) { throw new IllegalArgumentException ("年龄不能为负数:" + age); } if (age > 150 ) { throw new IllegalArgumentException ("年龄不能超过150:" + age); } System.out.println("年龄验证通过:" + age); } public void withdraw (double amount, double balance) throws InsufficientFundsException { if (amount <= 0 ) { throw new IllegalArgumentException ("取款金额必须大于0" ); } if (amount > balance) { throw new InsufficientFundsException ("余额不足,余额:" + balance + ",取款:" + amount); } System.out.println("取款成功,金额:" + amount); } public int getArrayElement (int [] arr, int index) { if (arr == null ) { throw new NullPointerException ("数组不能为null" ); } if (index < 0 || index >= arr.length) { throw new ArrayIndexOutOfBoundsException ("索引越界:" + index + ",数组长度:" + arr.length); } return arr[index]; } public static void main (String[] args) { ThrowDemo demo = new ThrowDemo (); try { demo.validateAge(-5 ); } catch (IllegalArgumentException e) { System.out.println("年龄验证异常:" + e.getMessage()); } try { demo.withdraw(1000 , 500 ); } catch (InsufficientFundsException e) { System.out.println("取款异常:" + e.getMessage()); } catch (IllegalArgumentException e) { System.out.println("参数异常:" + e.getMessage()); } try { int [] arr = {1 , 2 , 3 }; int value = demo.getArrayElement(arr, 5 ); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组访问异常:" + e.getMessage()); } } }
自定义异常 创建自定义异常类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public class InsufficientFundsException extends Exception { private double amount; private double balance; public InsufficientFundsException () { super ("余额不足" ); } public InsufficientFundsException (String message) { super (message); } public InsufficientFundsException (double amount, double balance) { super ("余额不足:尝试取款" + amount + ",当前余额" + balance); this .amount = amount; this .balance = balance; } public InsufficientFundsException (String message, Throwable cause) { super (message, cause); } public double getAmount () { return amount; } public double getBalance () { return balance; } public double getShortfall () { return amount - balance; } } public class InvalidUserInputException extends RuntimeException { private String inputValue; private String expectedFormat; public InvalidUserInputException (String inputValue, String expectedFormat) { super ("无效输入:'" + inputValue + "',期望格式:" + expectedFormat); this .inputValue = inputValue; this .expectedFormat = expectedFormat; } public InvalidUserInputException (String message, String inputValue, String expectedFormat) { super (message); this .inputValue = inputValue; this .expectedFormat = expectedFormat; } public String getInputValue () { return inputValue; } public String getExpectedFormat () { return expectedFormat; } } public abstract class BusinessException extends Exception { private String errorCode; private String errorMessage; public BusinessException (String errorCode, String errorMessage) { super (errorMessage); this .errorCode = errorCode; this .errorMessage = errorMessage; } public BusinessException (String errorCode, String errorMessage, Throwable cause) { super (errorMessage, cause); this .errorCode = errorCode; this .errorMessage = errorMessage; } public String getErrorCode () { return errorCode; } public String getErrorMessage () { return errorMessage; } @Override public String toString () { return String.format("[%s] %s" , errorCode, errorMessage); } } public class UserNotFoundException extends BusinessException { private String userId; public UserNotFoundException (String userId) { super ("USER_NOT_FOUND" , "用户不存在:" + userId); this .userId = userId; } public String getUserId () { return userId; } } public class ProductOutOfStockException extends BusinessException { private String productId; private int requestedQuantity; private int availableQuantity; public ProductOutOfStockException (String productId, int requestedQuantity, int availableQuantity) { super ("PRODUCT_OUT_OF_STOCK" , String.format("商品库存不足 - 商品ID:%s,请求数量:%d,可用数量:%d" , productId, requestedQuantity, availableQuantity)); this .productId = productId; this .requestedQuantity = requestedQuantity; this .availableQuantity = availableQuantity; } public String getProductId () { return productId; } public int getRequestedQuantity () { return requestedQuantity; } public int getAvailableQuantity () { return availableQuantity; } }
使用自定义异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 import java.util.HashMap;import java.util.Map;public class CustomExceptionDemo { private Map<String, Double> accounts; private Map<String, Integer> inventory; public CustomExceptionDemo () { accounts = new HashMap <>(); accounts.put("user001" , 1000.0 ); accounts.put("user002" , 2500.0 ); inventory = new HashMap <>(); inventory.put("PROD001" , 50 ); inventory.put("PROD002" , 0 ); } public void withdraw (String accountId, double amount) throws InsufficientFundsException { Double balance = accounts.get(accountId); if (balance == null ) { throw new IllegalArgumentException ("账户不存在:" + accountId); } if (amount <= 0 ) { throw new InvalidUserInputException (String.valueOf(amount), "正数金额" ); } if (amount > balance) { throw new InsufficientFundsException (amount, balance); } accounts.put(accountId, balance - amount); System.out.printf("取款成功 - 账户:%s,金额:%.2f,余额:%.2f%n" , accountId, amount, balance - amount); } public void purchaseProduct (String productId, int quantity) throws ProductOutOfStockException, UserNotFoundException { Integer stock = inventory.get(productId); if (stock == null ) { throw new IllegalArgumentException ("商品不存在:" + productId); } if (quantity <= 0 ) { throw new InvalidUserInputException (String.valueOf(quantity), "正整数数量" ); } if (quantity > stock) { throw new ProductOutOfStockException (productId, quantity, stock); } inventory.put(productId, stock - quantity); System.out.printf("购买成功 - 商品:%s,数量:%d,剩余库存:%d%n" , productId, quantity, stock - quantity); } public static void main (String[] args) { CustomExceptionDemo demo = new CustomExceptionDemo (); System.out.println("=== 银行取款测试 ===" ); try { demo.withdraw("user001" , 500.0 ); demo.withdraw("user001" , 800.0 ); } catch (InsufficientFundsException e) { System.out.println("取款失败:" + e.getMessage()); System.out.println("缺口金额:" + e.getShortfall()); } catch (InvalidUserInputException e) { System.out.println("输入无效:" + e.getMessage()); } catch (IllegalArgumentException e) { System.out.println("参数错误:" + e.getMessage()); } System.out.println("\n=== 商品购买测试 ===" ); try { demo.purchaseProduct("PROD001" , 30 ); demo.purchaseProduct("PROD001" , 30 ); } catch (ProductOutOfStockException e) { System.out.println("购买失败:" + e); System.out.println("商品ID:" + e.getProductId()); System.out.println("请求数量:" + e.getRequestedQuantity()); System.out.println("可用数量:" + e.getAvailableQuantity()); } catch (UserNotFoundException e) { System.out.println("用户异常:" + e); } catch (InvalidUserInputException e) { System.out.println("输入异常:" + e.getMessage()); } } }
异常处理最佳实践 异常处理原则 graph TD
A[异常处理最佳实践] --> B[捕获具体异常]
A --> C[合理的异常粒度]
A --> D[及时释放资源]
A --> E[记录异常信息]
A --> F[不要忽略异常]
A --> G[性能考虑]
B --> B1[避免捕获Exception]
B --> B2[按照从具体到抽象的顺序]
B --> B3[针对性处理不同异常]
C --> C1[不要过度使用异常]
C --> C2[异常用于异常情况]
C --> C3[正常流程不依赖异常]
D --> D1[使用try-with-resources]
D --> D2[finally块中释放资源]
D --> D3[避免资源泄漏]
E --> E1[记录异常堆栈]
E --> E2[包含上下文信息]
E --> E3[便于问题定位]
F --> F1[空catch块是禁忌]
F --> F2[至少记录日志]
F --> F3[考虑恢复策略]
G --> G1[异常创建有开销]
G --> G2[避免在循环中抛异常]
G --> G3[缓存常用异常]
实践示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 import java.io.*;import java.util.logging.Logger;import java.util.logging.Level;public class ExceptionBestPractices { private static final Logger logger = Logger.getLogger(ExceptionBestPractices.class.getName()); public void goodExceptionHandling (String filename) { try (BufferedReader reader = new BufferedReader (new FileReader (filename))) { String line = reader.readLine(); processLine(line); } catch (FileNotFoundException e) { logger.log(Level.WARNING, "文件未找到:" + filename, e); } catch (SecurityException e) { logger.log(Level.SEVERE, "没有读取文件权限:" + filename, e); } catch (IOException e) { logger.log(Level.SEVERE, "读取文件时发生IO异常:" + filename, e); } } public void badExceptionHandling (String filename) { try { BufferedReader reader = new BufferedReader (new FileReader (filename)); String line = reader.readLine(); processLine(line); reader.close(); } catch (Exception e) { } } public String readFileContent (String filename) throws IOException { StringBuilder content = new StringBuilder (); try (FileInputStream fis = new FileInputStream (filename); InputStreamReader isr = new InputStreamReader (fis, "UTF-8" ); BufferedReader reader = new BufferedReader (isr)) { String line; while ((line = reader.readLine()) != null ) { content.append(line).append("\n" ); } } catch (FileNotFoundException e) { logger.log(Level.WARNING, "文件不存在:" + filename, e); throw e; } catch (UnsupportedEncodingException e) { logger.log(Level.SEVERE, "不支持的编码:UTF-8" , e); throw e; } catch (IOException e) { logger.log(Level.SEVERE, "读取文件失败:" + filename, e); throw e; } return content.toString(); } public void convertException (String data) throws DataProcessingException { try { processData(data); } catch (NumberFormatException e) { throw new DataProcessingException ("数据格式错误:" + data, e); } catch (NullPointerException e) { throw new DataProcessingException ("数据为空" , e); } } public void performanceConsciousMethod (String[] data) { for (String item : data) { if (item == null || item.trim().isEmpty()) { logger.warning("跳过无效数据项" ); continue ; } try { processValidData(item); } catch (DataProcessingException e) { logger.log(Level.WARNING, "处理数据项失败:" + item, e); } } } public void comprehensiveExceptionLogging () { try { riskyOperation(); } catch (Exception e) { logger.log(Level.SEVERE, String.format("操作失败 - 时间:%s,用户:%s,操作:%s" , new java .util.Date(), getCurrentUser(), "riskyOperation" ), e); sendErrorReport(e); cleanup(); } } private void processLine (String line) throws DataProcessingException { if (line == null ) { throw new DataProcessingException ("行数据为空" ); } } private void processData (String data) throws NumberFormatException { Integer.parseInt(data); } private void processValidData (String data) throws DataProcessingException { } private void riskyOperation () throws Exception { } private String getCurrentUser () { return "currentUser" ; } private void sendErrorReport (Exception e) { } private void cleanup () { } } class DataProcessingException extends Exception { public DataProcessingException (String message) { super (message); } public DataProcessingException (String message, Throwable cause) { super (message, cause); } }
异常链和异常抑制 异常链(Exception Chaining) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class ExceptionChainingDemo { public void databaseOperation () throws DatabaseException { try { connectToDatabase(); } catch (SQLException e) { throw new DatabaseException ("数据库操作失败" , e); } } public void businessLogic () throws BusinessLogicException { try { databaseOperation(); } catch (DatabaseException e) { throw new BusinessLogicException ("业务逻辑执行失败" , e); } } private void connectToDatabase () throws SQLException { throw new SQLException ("连接数据库失败:网络超时" ); } public static void main (String[] args) { ExceptionChainingDemo demo = new ExceptionChainingDemo (); try { demo.businessLogic(); } catch (BusinessLogicException e) { System.out.println("顶层异常:" + e.getMessage()); Throwable cause = e.getCause(); while (cause != null ) { System.out.println("原因:" + cause.getMessage()); cause = cause.getCause(); } e.printStackTrace(); } } } class DatabaseException extends Exception { public DatabaseException (String message, Throwable cause) { super (message, cause); } } class BusinessLogicException extends Exception { public BusinessLogicException (String message, Throwable cause) { super (message, cause); } }
异常抑制(Suppressed Exceptions) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class SuppressedExceptionDemo { public void demonstrateSuppressedException () { try (CloseableResource resource1 = new CloseableResource ("Resource1" ); CloseableResource resource2 = new CloseableResource ("Resource2" )) { throw new RuntimeException ("业务逻辑异常" ); } catch (Exception e) { System.out.println("主异常:" + e.getMessage()); Throwable[] suppressed = e.getSuppressed(); for (Throwable t : suppressed) { System.out.println("抑制的异常:" + t.getMessage()); } } } public static void main (String[] args) { new SuppressedExceptionDemo ().demonstrateSuppressedException(); } } class CloseableResource implements AutoCloseable { private String name; public CloseableResource (String name) { this .name = name; System.out.println("创建资源:" + name); } @Override public void close () throws Exception { System.out.println("关闭资源:" + name); throw new RuntimeException ("关闭资源时异常:" + name); } }
总结 Java异常处理机制是程序健壮性的重要保障,本文全面介绍了异常处理的各个方面:
异常基础 :理解异常的概念、层次结构和分类
异常类型 :掌握Error、运行时异常和编译时异常的区别
处理机制 :熟练使用try-catch-finally和try-with-resources
异常传播 :理解throws声明和throw抛出的使用
自定义异常 :学会创建和使用业务相关的自定义异常
最佳实践 :掌握异常处理的原则和性能考虑
高级特性 :了解异常链和异常抑制机制
良好的异常处理不仅能提高程序的稳定性,还能为用户提供更好的体验,为开发者提供有效的调试信息。在实际开发中,应该根据具体场景选择合适的异常处理策略,既要保证程序的健壮性,也要考虑性能和可维护性。
参考资料
Oracle Java Documentation - Exception Handling
Effective Java by Joshua Bloch - Exception Handling
Java核心技术卷I - 异常处理
Clean Code by Robert Martin - Error Handling abbrlink: 2