1. 异常基础:什么是异常及其重要性
异常(Exception)是Java中表示错误或异常情况的对象,它会中断程序的正常执行流程。异常机制使程序能优雅处理错误,避免崩溃,并提升代码可读性和可维护性。
关键概念:
-
常见异常场景(来自文档):
- 算术错误: 如除零操作(ArithmeticException)。
- 输入问题: 如无效用户输入(InputMismatchException)。
- 访问问题: 如无效数组索引(ArrayIndexOutOfBoundsException)或空引用访问(NullPointerException)。
-
为什么使用异常处理:
- 防止系统崩溃:错误被捕获后,程序可继续运行(如显示错误消息而非终止)。
- 隔离错误管理:业务逻辑与错误处理代码分离,提升可读性。
- 确保连续性:程序在错误后能恢复执行(如重新提示用户输入)。
- 提升代码质量:通过结构化处理,使代码更健壮和可维护。
场景示例:数组访问问题
当访问无效数组索引时,Java抛出ArrayIndexOutOfBoundsException。
原始代码:
int[] numbers = {1, 2, 3}; // 数组初始化
int value = numbers[5]; // 索引5超出范围,抛出异常
此错误可通过异常处理修复,例如使用try-catch块捕获异常。
场景示例:用户输入问题
处理用户输入时,无效输入(如非数字值)会导致InputMismatchException。
原始代码:
Scanner scanner = new Scanner(System.in);
System.out.print("Enter a number: ");
int number = scanner.nextInt(); // 输入文本时抛出异常
解决方法:使用try-catch捕获异常,并提示用户重试。
2. 异常处理机制:try-catch-finally和throw
Java通过try-catch-finally块处理异常,throw关键字用于手动抛出异常。核心机制确保错误隔离和资源清理。
2.1 try-catch-finally块
- 结构:
try
:包裹可能抛出异常的代码。catch
:捕获并处理特定异常。finally
:无论是否发生异常,都执行(常用于资源清理)。
- 执行流程:
- 无异常时:执行try块后跳过catch,执行finally。
- 有异常时:立即跳转至匹配的catch块,然后执行finally。
- finally始终执行,即使try或catch中有return语句。
示例:除法程序(Quotient.java)
原始程序在除零时崩溃:
import java.util.Scanner;
public class Quotient {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter two integers: ");
int number1 = input.nextInt();
int number2 = input.nextInt();
System.out.println(number1 + "/" + number2 + " is " + (number1 / number2)); // 除零时抛出ArithmeticException
}
}
输出:
- 输入5 2:输出"5/2 is 2"。
- 输入3 0:抛出ArithmeticException,程序崩溃。
解决方案1:输入验证(使用if-else)
import java.util.Scanner;
public class QuotientWithValidation {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter two integers: ");
int number1 = input.nextInt();
int number2 = input.nextInt();
if (number2 != 0) {
System.out.println(number1 + "/" + number2 + " is " + (number1 / number2));
} else {
System.out.println("Cannot divide by zero!"); // 处理除零错误
}
}
}
优点: 简单直接。
缺点: 错误处理与业务逻辑混杂,不适用于复杂场景。
解决方案2:方法封装(使用System.exit)
import java.util.Scanner;
public class QuotientWithMethod {
public static int quotient(int number1, int number2) {
if (number2 == 0) {
System.out.println("Divisor cannot be zero");
System.exit(1); // 终止程序
}
return number1 / number2;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter two integers: ");
int number1 = input.nextInt();
int number2 = input.nextInt();
int result = quotient(number1, number2);
System.out.println(number1 + "/" + number2 + " is " + result);
}
}
缺点: System.exit()粗暴终止程序,缺乏灵活性。
解决方案3:异常处理(使用try-catch)
import java.util.Scanner;
public class QuotientWithException {
public static int quotient(int number1, int number2) {
if (number2 == 0)
throw new ArithmeticException("Divisor cannot be zero"); // 手动抛出异常
return number1 / number2;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter two integers: ");
int number1 = input.nextInt();
int number2 = input.nextInt();
try {
int result = quotient(number1, number2);
System.out.println(number1 + "/" + number2 + " is " + result);
} catch (ArithmeticException ex) {
System.out.println("Exception: an integer cannot be divided by zero"); // 捕获异常
}
System.out.println("Execution continues...");
}
}
优点:
- 错误隔离:业务逻辑(除法)与错误处理分离。
- 程序连续性:异常处理后,继续执行后续代码。
- 灵活性:可扩展处理多种异常类型。
2.2 throw关键字和异常传播
- throw: 手动抛出异常对象(如
throw new ArithmeticException("message")
)。 - 异常传播: 异常未被捕获时,向上层调用方法传播,直至被处理或导致程序终止。
示例:异常传播
package Exception;
class ExceptionPropagation {
void m() {
int data = 50 / 0; // 抛出ArithmeticException
}
void n() {
m(); // 异常传播至n()
}
void p() {
try {
n(); // 异常传播至p()
} catch (Exception e) {
System.out.println("Exception handled: " + e); // 捕获并处理
}
}
public static void main(String[] args) {
ExceptionPropagation obj = new ExceptionPropagation();
obj.p();
System.out.println("Program continues normally...");
}
}
输出:
Exception handled: java.lang.ArithmeticException: / by zero
Program continues normally...
流程: 异常在m()中抛出,传播至n()和p(),最终被p()的catch块捕获,程序继续执行。
2.3 finally块的作用
示例:Scanner资源清理
import java.util.Scanner;
public class FinallyDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Please enter a divisor");
try {
int divisor = scanner.nextInt();
int quotient = 10 / divisor;
System.out.println(quotient);
} catch (InputMismatchException e) {
System.err.println("Sorry, your input is invalid.");
} catch (ArithmeticException e) {
System.err.println("Sorry, unable to divide by 0");
} finally {
scanner.close(); // 无论是否异常,都关闭Scanner
System.out.println("Scanner closed in finally block.");
}
}
}
关键点:
- finally确保资源(如文件、数据库连接)被清理。
- 执行场景:
- 无异常时:执行try后执行finally。
- 有异常时:执行catch后执行finally。
- 异常未捕获时:先执行finally,再传播异常。
3. 异常类层次和类型
Java异常基于类层次结构,根类为Throwable。异常分为Error、Exception(checked)和RuntimeException(unchecked)。
异常类层次:
详细对比:
-
Checked Exception(必须处理):
- 编译器强制处理(通过try-catch或throws声明)。
- 代表可恢复错误,如外部因素(文件不存在、网络中断)。
- 示例:IOException、SQLException。
-
Unchecked Exception(RuntimeException):
- 编译器不强制处理,通常由编程错误引起(如空指针、除零)。
- 示例:NullPointerException、ArithmeticException。
- 最佳实践:通过代码修复而非捕获(如空值检查)。
-
Error:
- 系统级严重错误(如内存溢出),JVM抛出,不建议捕获。
4. 自定义异常
自定义异常通过继承Exception类创建,用于特定错误场景(如业务规则违规),提升错误信息的清晰度。
实现步骤:
- 创建类继承Exception(或其子类)。
- 添加构造器(如带消息的构造器)。
- 在代码中通过throw抛出。
示例:基本自定义异常
class MyException extends Exception {
public MyException(String message) {
super(message); // 调用父类构造器
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
try {
throw new MyException("This is a custom exception!"); // 抛出自定义异常
} catch (MyException e) {
System.out.println("Exception caught: " + e.getMessage()); // 捕获并处理
}
}
}
输出:
Exception caught: This is a custom exception!
完整示例(带多个构造器):
class CompleteException extends Exception {
public CompleteException() {
super(); // 无参构造器
}
public CompleteException(String message) {
super(message); // 带消息构造器
}
public CompleteException(Throwable cause) {
super(cause); // 带原因构造器
}
public CompleteException(String message, Throwable cause) {
super(message, cause); // 带消息和原因构造器
}
}
// 使用示例
public class CustomExceptionUsage {
public static void validateAge(int age) throws CompleteException {
if (age < 18) {
throw new CompleteException("Age must be at least 18"); // 抛出自定义异常
}
}
public static void main(String[] args) {
try {
validateAge(15);
} catch (CompleteException e) {
System.out.println("Custom exception handled: " + e.getMessage());
}
}
}
适用场景: 当标准异常无法精确描述错误时(如课程注册中的年龄限制)。
6. 最佳实践和总结
关键总结:
- 异常是对象: 通过类层次管理(Throwable为根)。
- 结构化处理: 使用try-catch-finally分离错误逻辑,确保资源清理。
- 传播机制: 异常向上传播,直至被捕获。
- 类型区分: Checked异常必须处理,unchecked异常建议代码修复。
- 自定义异常: 提升错误信息的业务相关性。
最佳实践:
-
库方法异常:检查文档(如Scanner.nextInt可能抛出InputMismatchException)。
-
资源管理: 始终在finally中关闭资源(如文件、数据库)。
-
避免滥用: 不要捕获所有异常(如catch(Exception e)),应针对特定类型处理。