Java异常处理
异常是程序中的一些错误,但并不是所有的错误都是异常,比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
一、异常的层次结构
异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。

1.1.Throwable
- Throwable 是 Java 语言中所有错误与异常的超类。
- Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生的异常情况。
- Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
1.2.Error(错误)
Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
这里错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
1.3.Exception(异常)
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
- 运行时异常
都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
- 非运行时异常 (编译异常)
是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
1.4.可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)
- 可查异常(编译器要求必须处置的异常):
正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
- 不可查异常(编译器不要求强制处置的异常)
包括运行时异常(RuntimeException与其子类)和错误(Error)。
二、异常
2.1.异常关键字
- try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw – 用于抛出异常。
- throws – 用在方法签名中,用于声明该方法可能抛出的异常。
2.2.异常的申明(throws)
在Java中,当前执行的语句必属于某个方法,Java解释器调用main方法执行开始执行程序。若方法中存在检查异常,如果不对其捕获,那必须在方法头中显式声明该异常,以便于告知方法调用者此方法有异常,需要进行处理。 在方法中声明一个异常,方法头中使用关键字throws,后面接上要声明的异常。若声明多个异常,则使用逗号分割。如下所示:
public class Test { public static void main(String[] args) throws IOException, FileNotFoundException { //声明异常 } }
注意:若是父类的方法没有声明异常,则子类继承方法后,也不能声明异常。
通常,应该捕获的是知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法名后面使用 throws 关键字声明可能会抛出的异常。
private static void readFile(String filePath) throws IOException { File file = new File(filePath); String result; BufferedReader reader = new BufferedReader(new FileReader(file)); while((result = reader.readLine())!=null) { System.out.println(result); } reader.close(); }
2.2.异常的抛出(throw)
throw 关键字用于在当前方法中抛出一个异常。通常情况下,当代码执行到某个条件下无法继续正常执行时,可以使用 throw 关键字抛出异常,以告知调用者当前代码的执行状态。
例如,下面的代码中,在方法中判断 num1 是否小于num2,如果是,则抛出一个 IllegalArgumentException 异常。
public void checkNumber(int num1, int num2){ if(num1<num2){ //抛出异常 throw new IllegalArgumentException("num1 大于 num2"); } }
2.3.异常的捕获
异常捕获处理的方法通常有:
2.3.1try-catch
使用 try 和 catch 关键字可以捕获异常。try代码块中的代码称为保护代码,语法如下:
try { // 程序代码 }catch(ExceptionName e1) { //Catch 块 }
Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。
public static void main(String[] args){ //异常的捕获 try { System.out.println(5/0); }catch (ArithmeticException a){ System.out.println(a); //java.lang.ArithmeticException: / by zero } }
同一个 catch 也可以捕获多种类型异常,用 | 隔开
public static void main(String[] args){ //异常的捕获 try { int a[] = {23,12,45,12,56};//java.lang.ArrayIndexOutOfBoundsException: 10 System.out.println(a[10]); System.out.println(5/0); }catch (ArithmeticException | ArrayIndexOutOfBoundsException e){ System.out.println(e); //java.lang.ArithmeticException: / by zero }catch (Exception e){ System.out.println(e); } }
2.3.2.try-catch-finally
finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。语法规则:
try { //执行程序代码,可能会出现异常 } catch(Exception e) { //捕获异常并处理 } finally { //必执行的代码 }
执行的顺序
- 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
- 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
- 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
public static void main(String[] args){ //异常的捕获 try { int a[] = {23,12,45,12,56};//java.lang.ArrayIndexOutOfBoundsException: 10 System.out.println(a[10]); System.out.println(5/0); }catch (ArithmeticException | ArrayIndexOutOfBoundsException e){ System.out.println(e); //java.lang.ArithmeticException: / by zero }catch (Exception e){ System.out.println(e); }finally { System.out.println("我是finally语句块,无论是的有异常都要执行"); } }
2.3.3.try-finally
可以直接使用try-finally,try块中引起异常,异常代码之后的语句不再执行,直接执行finally语句。 try块没有引发异常,则执行完try块就执行finally语句。
try-finally可用在不需要捕获异常的代码,可以保证资源在使用后被关闭。例如IO流中执行完相应操作后,关闭相应资源;使用Lock对象保证线程同步,通过finally可以保证锁会被释放;数据库连接代码时,关闭连接操作等等。
//以Lock加锁为例,演示try-finally ReentrantLock lock = new ReentrantLock(); try { //需要加锁的代码 } finally { lock.unlock(); //保证锁一定被释放 }
finally遇见如下情况不会执行
- 在前面的代码中用了System.exit()退出程序。
- finally语句块中发生了异常。
- 程序所在的线程死亡。
- 关闭CPU。
2.3.4.try-with-resource
为了解决下面的代码就需要再finally中再次捕获处理异常。当程序使用 finally 块关闭资源时,程序会显得异常臃肿,例如以下代码。
public void tryWithResourceTest(){ BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter("1.txt")); writer.write("你好!大师"); }catch (IOException e){ System.out.println(e); }finally { try{ if(writer != null){ writer.close(); } } catch (IOException e) { //捕获异常 e.printStackTrace(); } } }
Java 7 以前,上面程序中的 finally 代码块是不得不写的“臃肿代码”,为了解决这种问题,Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件,被称为自动资源管理(Automatic Resource Management)。该特性是在 try 语句上的扩展,主要释放不再需要的文件或其他资源。
自动资源管理替代了 finally 代码块,并优化了代码结构和提高程序可读性。语法如下:
try (声明或初始化资源语句) { // 可能会生成异常语句 } catch(Throwable e1){ // 处理异常e1 } catch(Throwable e2){ // 处理异常e1 } catch(Throwable eN){ // 处理异常eN }
当 try 代码块结束时,自动释放资源。不再需要显式的调用 close() 方法,该形式也称为“带资源的 try 语句”。
注意:
- try 语句中声明的资源被隐式声明为 final,资源的作用局限于带资源的 try 语句。
- 可以在一条 try 语句中声明或初始化多个资源,每个资源以
;隔开即可。 - 需要关闭的资源必须实现了 AutoCloseable 或 Closeable 接口。
- Closeable 是 AutoCloseable 的子接口,Closeable 接口里的 close() 方法声明抛出了 IOException,因此它的实现类在实现 close() 方法时只能声明抛出 IOException 或其子类;AutoCloseable 接口里的 close() 方法声明抛出了 Exception,因此它的实现类在实现 close() 方法时可以声明抛出任何异常。
下面示范如何使用自动关闭资源的 try 语句。
package com.array; import java.io.BufferedWriter; import java.io.FileWriter; public class Test1 { public static void main(String[] args){ try( // 声明、初始化一个可关闭的资源 // try语句会自动关闭这个资源 BufferedWriter writer = new BufferedWriter(new FileWriter("1.txt")); ) { writer.write("helloWorld"); }catch (Exception e){ System.out.println("异常信息为:"+e); } } }
上面代码中代码分别声明、初始化了一个 IO 流,BufferedWriter都实现了 Closeable 接口,并在 try 语句中进行了声明和初始化,所以 try 语句会自动关闭它们。
- 自动关闭资源的 try 语句相当于包含了隐式的 finally 块(这个 finally 块用于关闭资源),因此这个 try 语句可以既没有 catch 块,也没有 finally 块。
- Java 7 几乎把所有的“资源类”(包括文件 IO 的各种类、JDBC 编程的 Connection 和 Statement 等接口)进行了改写,改写后的资源类都实现了 AutoCloseable 或 Closeable 接口。
注意:如果程序需要,自动关闭资源的 try 语句后也可以带多个 catch 块和一个 finally 块。
2.4.自定义异常
如果 Java 提供的内置异常类型不能满足程序设计的需求,这时我们可以自己设计 Java 类库或框架,其中包括异常类型。实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类。
自定义异常的语法形式为:
<class><自定义异常名><extends><Exception>
在编码规范上,一般将自定义异常类的类名命名为 XXXException,其中 XXX 用来代表该异常的作用。
自定义异常类一般包含两个构造方法:
- 一个是无参的默认构造方法
- 另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。
创建一个名称为 IntegerRangeException 的自定义异常类:
package com.array; public class IntegerRangeException extends Exception{ public IntegerRangeException() { super(); } public IntegerRangeException(String s) { super(s); } }
上面的代码创建的自定义异常类 IntegerRangeException 类继承自 Exception 类,在该类中包含两个构造方法。
案例如下:编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0~100 岁。
1)这里创建了一个异常类 MyException,并提供两个构造方法。MyException 类的实现代码如下:
package com.array; public class MyException extends Exception{ public MyException() { super(); } public MyException(String s) { super(s); } }
2)接着创建测试类,调用自定义异常类。代码实现如下:
package com.array; import java.util.InputMismatchException; import java.util.Scanner; public class Test02 { public static void main(String[] args){ int age; //创建scanner Scanner scanner = new Scanner(System.in); //提示 System.out.print("请输入您的年龄:"); try { age = scanner.nextInt(); if(age>100){ //抛出异常 throw new MyException("您输入的年龄大于100!输入有误!"); }else if(age<0){ //抛出异常 throw new MyException("您输入的年龄小于0!输入有误!"); }else { System.out.println("您的年龄为:"+age); } }catch (InputMismatchException e){ System.out.println("输入的年龄不是数字!"); }catch (MyException e2){ //获取抛出的异常信息 System.out.println(e2.getMessage()); } } }
上面的主方法中,使用了 if…else if…else 语句结构判断用户输入的年龄是否为负数和大于 100 的数,如果是,则拋出自定义异常 MyException,调用自定义异常类 MyException 中的含有一个 String 类型的构造方法。在 catch 语句块中捕获该异常,并调用 getMessage() 方法输出异常信息。

浙公网安备 33010602011771号