在程序设计和运行的过程中,尽管 Java 提供了便于写出简洁、安全代码的方法,并且程序员也尽可能规避错误,但使程序被迫停正的错误仍然不可避免。为此,Java 提供了异常处理机制来帮助程序员检查可能出现的错误,提高了程序的可读性和可维护性。
异常指的是程序在执行过程中,出现的非正常的情况,最终会导致 JVM 的非正常停止。异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。在 Java 等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java 处理异常的方式是中断处理。
编译时期异常:checked 异常。在编译时期,就会检查,如果没有处理异常,则编译失败。 运行时期异常:runtime异常。在运行时期,检查异常,在编译时期,运行异常不会编译器检测(不报错)。
常见异常
ParseException
:解析异常
NullPointerException
:空指针异常
ClassCastException
:类型转换异常
ArithmeticException
:数学运算异常
NumberFormatException
:数字格式化异常
ArrayIndexOutOfBoundsException
:数组下标越界异常
方法名 | 说明 |
---|---|
void printStackTrace( ) | 将该异常的跟踪栈信息输出到标准错误输出 |
void printStackTrace( ) | 将该异常的跟踪栈信息输出到指定输出流 |
String getMessage( ) | 返回该异常的详细描述字符串 |
StackTraceElement[ ] getStackTrace( ) | 返回异常的跟踪栈信息 |
如果执行 try 块里的业务逻辑代码时出现异常,JVM 创建一个异常对象,提交给异常发生的方法,这个过程被称为抛出(throw)异常。当接收到异常对象后,会寻找能处理该异常对象的 catch 块,如果找到合适的 catch 块,则把该异常对象交给该 catch 块处理,这个过程被称为捕获(catch)异常;如果找不到捕获异常的 catch 块,则程序终止。
try {
编写可能会出现异常的代码
} catch(异常类型 e){
处理异常的代码
}
public class Test {
public static void main(String[] args) {
System.out.println("########## 程序开始 ##########");
try {
System.out.println("########## 进入 try ##########");
// 抛出异常后,直接进入 catch 中,不会执行 try 中剩余代码
int i = 1 / 0;
System.out.println("########## try 结束 ##########");
} catch (Exception e) {
System.out.println("########## 处理异常 ##########");
e.printStackTrace();
}
System.out.println("########## 程序结束 ##########");
}
}
try 块后可以有多个 catch 块(捕获父类异常的 catch 块必须位于捕获子类异常的后面),这是为了针对不同的异常类提供不同的异常处理方式。当系统产生不同的异常时,系统会生成不同的异常对象,根据该异常对象所属的异常类来决定使用哪个 catch 块来处理该异常。 如果 try 块被执行一次,则 try 块后只有一个 catch 块会被执行,绝不可能有多个 catch 块被执行。
try 块后的花括号{ }
不可以省略,即使 try 块里只有一行代码,也不可省略这个花括号。与之类似的是,catch块后的花括号{ }
也不可以省略。try 块里声明的变量是局部变量,它只在 try 块内有效,在 catch 块中不能访问该变量。
异常处理机制提供了 finally 块。不管 try 块中的代码是否出现异常,也不管哪一个 catch 块被执行,甚至在 try 块或 catch 块中执行了 return 语句,finally 块总会被执行。
try{
编写可能会出现异常的代码
}catch(异常类型 e){
处理异常的代码
}finally{
不论怎样都要执行的代码
}
public class Test {
public static void main(String[] args) {
System.out.println("########## 程序开始 ##########");
int[] arr = {1, 2, 3};
try {
int i = 1 / 0;
} catch (Exception e) {
System.out.println(arr[3]);
} finally {
// 不论怎样都会执行 finally 中的代码
System.out.println("########## finally ##########");
}
System.out.println("########## 程序结束 ##########");
}
}
异常处理语法结构中只有 try 块是必需的,也就是说,如果没有 try 块,则不能有后面的 catch 块和 finally 块;catch 块和 finally 块都是可选的,可以同时出现,catch 块和 finally 块至少出现其中之一;不能只有 try 块,既没有 catch 块,也没有 finally 块;多个 catch 块必须位于 try 块之后,finally 块必须位于所有的 catch 块之后。
系统是否要抛出异常,可能需要根据应用的业务需求来决定,如果程序中的数据、执行与既定的业务需求不符,这就是一种异常。由于与业务需求不符而产生的异常,必须由程序员来决定抛出,系统无法抛出这种异常。
throw new 异常类型(参数);
public class Test {
public static void main(String[] args) {
try {
throw new ArrayIndexOutOfBoundsException("抛出了数组索引越界异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
在通常情况下,异常的类名通常也包含了该异常的有用信息。所以在选择抛出异常时,应该选择合适的异常类,从而可以明确地描述该异常情况。在这种情形下,应用程序常常需要抛出自定义异常。
自定义异常都应该继承 Exception 基类,如果希望自定义 Runtime 异常,则应该继承 RuntimeException 基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器;另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的 getMessage( ) 方法的返回值)。
// 自定义异常
public class MyException extends Exception {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
---------------------------------------------------------------
// 测试类
public class Test {
public static void main(String[] args) {
try {
throw new MyException("这是个自定义异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}