推荐学习专栏:
在Java语言中,将程序执行中发生的不正常情况称为异常 , 开发过程中的语法错误和逻辑错误不是异常,此时无法通过编译,程序无法运行,异常是指程序运行过程中的问题。

示例:

public class Test {    public static void main(String[] args) {        int a = 10;        int b = 0;        int c = a / b;        System.out.println(a + "/" + b + "=" + c);    }}

当程序运行到int c = a / b时,JVM 会 new 一个异常对象:

new ArithmeticException("/ by zero");

并且 JVM 将异常对象抛出,打印输出信息到控制台。

Java 中异常发生的原因有很多,例如:

  • 用户输入了非法数据
  • 要打开的文件不存在
  • 网络通信时连接中断
  • JVM内存溢出
  • 这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

2. 异常的体系

Java 异常其实是为了帮助我们找到程序中的问题,其本质其实是一个类,产生异常就是创建异常对象并抛出这个异常对象。

异常的根类是 Java.lang.Trowable,是所有异常和错误是超类,其有两个子类,分别是 Error 类和 Exception 类。平时我们常说的异常主要是这里的 Exception 类,这里也主要是对该类就行讲解。

Exception 类是指编译期异常类,即编译Java代码即写代码期出现的问题,其子类 RutimeException 类称为运行期异常,指 Java 运行中出现的问题,只要我们处理掉异常,程序就可以继续执行。Error 类是程序无法处理的错误,表示运行应用程序中较严重问题。

Java异常分为可查异常和不可查异常。正确的程序在运行过程中,很容易出现情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。

除了 RuntimeException 及其子类以外,其他的Exception类及其子类都属于可查异常,最典型的是IO类异常。

RuntimeException 与其子类和错误 Error 属于不可查异常,编译器不要求强制处置的异常。

3. Error

Error 是指系统内部的错误,这类错误由系统进行处理,程序本身无需捕获处理。比如:内存溢出错误OOM,堆栈溢出错误StackOverflowError等,一般发生这种情况,JVM会选择终止程序。此时必须修改代码,程序才能执行。

示例:

//堆栈溢出错误public class Test {    public static void recursionMethod() {        recursionMethod();// 无限递归下去    }    public static void main(String[] args) {        recursionMethod();    }}

报错信息:

Exception in thread "main" java.lang.StackOverflowErrorat Test.recursionMethod(Test.java:4)at Test.recursionMethod(Test.java:4)at Test.recursionMethod(Test.java:4)at Test.recursionMethod(Test.java:4)    ...

4. 异常产生的过程

通过了解异常产生的过程我们可以深入学习如何处理异常,通过下面的例子来学习异常产生的过程,定义一个数组,定义一个获取数组指定索引位置元素的值,此时程序就有可能产生数组索引越界的异常:

public class Test {    public static void main(String[] args) {        int[] arr={1,2,3};        int b=getElement(arr,3);        System.out.println(b);    }    public static int getElement(int[] arr,int index){        int a=arr[index];        return a;    }}

异常信息:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3at Test.getElement(Test.java:8)at Test.main(Test.java:4)

程序在执行 getElement() 方法时,传入了 arr 和3两个参数,目的是访问数组 arr 索引为3的元素的值,但是由于数组 arr 的长度为3,此时 JVM就会检测到程序出现了异常,JVM执行一下操作:

第一步: JVM根据异常的原因创建了一个异常的对象,包含了异常产生的内容,原因和位置。

new ArrayIndexOutOfBoundsException("3");

第二步:因为 getElement() 方法中没有异常处理的逻辑(try…catch),所以 JVM 把异常对象抛给了该方法的调用者 main 方法来处理这个异常。

当 main 方法接收到这个异常对象后,由于它也没有异常处理的逻辑,所以继续把异常对象抛给了 main 方法的调用者 JVM 来处理。当 JVM 接收到这个异常对象后,首先把异常对象的内容输出打印在控制台,其次采用中断处理,结束这个 Java 程序。

5. throw 关键字

我们可以使用 throw 在指定的方法中抛出异常,使用时必须写在方法的内部,且 throw 后创建的对象必须是 Exception 类的对象或者是其子类的对象。

throw 抛出异常后,我们必须处理这个异常,如果 throw 后创建的是 RuntimeException 类或者其子类的对象,我们可以不处理,默认交给 JVM 处理,即打印异常,中断程序。如果创建的是编译时(写代码时)异常对象,此时必须使用 throws 或者 try…catch 来处理异常。

示例:在定义一个方法时,我们要先对方法传递的参数进行合法性校验,如果传递的参数不合法,我们就要使用抛出异常的方法告知方法的调用者参数不合法。

public class Test {    public static void main(String[] args) {        int[] arr=null;        int b=getElement(arr,3);        System.out.println(b);    }    public static int getElement(int[] arr,int index){        if(arr==null) {            throw new NullPointerException("传递的数组为空");        }        if(index<0||index>=arr.length){            throw new ArrayIndexOutOfBoundsException("传递的参数超出数组下标") ;        }        int a=arr[index];        return a;    }}

此时,由于传入的数组为空值,且new NullPointerException("传递的数组为空")为运行时异常类的对象,所以异常信息会被打印在控制台,并且中断程序。

6. 异常处理

6.1 throws 关键字

使用 throws 关键字处理异常是今天要谈论的异常处理的第一种方式。前面说到,在方法中使用 throw 抛出异常对象后,必须处理这个异常对象,此时我们就可以使用 throws 把这个异常对象抛给方法的调用者,最终抛给 JVM 做中断处理。

使用 throws 关键字有以下几个注意事项:

  • throws 必须写在方法声明后面
  • throws 关键字后面使用的必须是 Exception 类或者其子类
  • 方法体中抛出多个异常对象就要在 throws 后面声明多个异常,如果方法体中抛出的异常有字父类的关系,则只需要声明父类异常

当我们调用了声明异常的方法后,就必须对这个异常进行处理,要么继续使用 throws 抛出异常对象交给方法的调用者处理,最终由 JVM 做中断处理。要么使用 try…catch 自己处理异常。

示例:定义一个方法,对方法输入的文件路径的参数做合法性判断。

import java.io.FileNotFoundException;public class Test {    public static void main(String[] args) throws FileNotFoundException {        //定义一个方法,对传递的路径参数做合法性校验        readFile("c://b.txt");    }    public static void readFile(String fileName) throws FileNotFoundException {        if (!(fileName.equals("c://a.txt"))) {            throw new FileNotFoundException("传递的文件路径不是c://a.txt");        }    }}

此时,如果 readFile() 方法传入的参数不是 "c://a.txt" ,程序运行到调用该方法的位置时,JVM 对程序做中断处理,打印异常内容到控制台。

6.2 try…catch 捕获异常

现在要谈论的是异常处理的第二种方法,使用 try…catch 捕获异常。前面我们使用 throws 声明异常,显然是有局限性的,程序运行到调用声明异常的方法时,如果程序出现异常,则后序代码不会执行。而此方法可以对程序中出现的问题做指定方式的处理。

语法:

try{    //可能出现异常的代码}catch(){//括号中定义异常变量,用来接收try中抛出的异常对象,try中抛出多个异常对象,可以使用多个catch处理异常对象    //异常处理逻辑   }

如果 try 中出现了异常,就会抛出异常对象并执行 catch 中的语句,执行完继续执行 try…catch 后的语句。否则不会执行 catch 中的语句,执行完 try 中的语句直接执行 try…catch 后的语句。

示例:定义一个方法,对方法输入的文件路径的参数做合法性判断,如果出现异常,则捕获异常。

import java.io.FileNotFoundException;public class Test {    public static void main(String[] args) {        try {            //定义一个方法,对传递的路径参数做合法性校验            readFile("c://b.txt");        } catch (FileNotFoundException e) {            System.out.println("传递的文件路径不是c://a.txt");        }        System.out.println("后续代码");    }    public static void readFile(String fileName) throws FileNotFoundException {        if (!(fileName.equals("c://a.txt"))) {            throw new FileNotFoundException("传递的文件路径不是c://a.txt");        }    }}

此时,如果 readFile() 方法中传入的参数不是 c://txt ,则捕获异常给 catch 中的语句块做异常处理,执行完以后继续执行 try…catch 后面的语句。

7. 获取异常信息

Throwable 中定义了三个方法用于获取异常信息,分别是:

  • public String getMessage() 返回 Trowable 的简短描述
  • public String toString() 返回 trowable 的详细描述
  • public String printStackTrace JVM 打印异常信息默认调用此方法,其描述最详细,包括异常产生的内容原因和位置

示例1:使用 getMessage() 方法获取异常信息

import java.io.FileNotFoundException;public class Test {    public static void main(String[] args) {        try {            //定义一个方法,对传递的路径参数做合法性校验            readFile("c://b.txt");        } catch (FileNotFoundException e) {            System.out.println(e.getMessage());        }    }    public static void readFile(String fileName) throws FileNotFoundException {        if (!(fileName.equals("c://a.txt"))) {            throw new FileNotFoundException("传递的文件路径不是c://a.txt");        }    }}

此时,如果程序出现异常,异常信息显示为:

示例2:使用 toString() 方法获取异常信息

 try {            //定义一个方法,对传递的路径参数做合法性校验            readFile("c://b.txt");        } catch (FileNotFoundException e) {            System.out.println(e.toString());        }

异常信息为:

示例3:使用 printStackTrace() 方法获取异常信息:

 try {            //定义一个方法,对传递的路径参数做合法性校验            readFile("c://b.txt");        } catch (FileNotFoundException e) {            e.printStackTrace();        }

此时的异常信息为:

8. finally 代码块

在使用 try…catch 捕获异常时,如果 try 中代码出现了异常,则创建异常对象抛给 catch 语句块中的异常处理逻辑代码处理,此时 try 中后续的代码将不会执行。有时我们需要这个位置的代码继续执行,例如需要释放资源,则出现了 fianlly 代码块。

无论程序是否出现异常,finally 代码块中的代码都会执行。且 finally 不可以单独使用,必须使用在 try 代码后,后序 IO 流中继续学习。

示例:

import java.io.FileNotFoundException;public class Test {    public static void main(String[] args) {        try {            //定义一个方法,对传递的路径参数做合法性校验            readFile("c://b.txt");        } catch (FileNotFoundException e) {            e.printStackTrace();        }finally {            System.out.println("资源释放");        }    }    public static void readFile(String fileName) throws FileNotFoundException {        if (!(fileName.equals("c://a.txt"))) {            throw new FileNotFoundException("传递的文件路径不是c://a.txt");        }    }}

此时,程序出现了异常,但是 finally 中的语句继续执行。

java.io.FileNotFoundException: 传递的文件路径不是c://a.txtat Test.readFile(Test.java:17)at Test.main(Test.java:7)资源释放

9. 自定义异常

系统定义的异常主要用来处理系统可以预见的常见运行错误,对于某个应用所特有的运行错误,需要编程人员根据特殊逻辑来创建自己的异常类。

例如在我们输入成绩的时候,往往会有一个范围,而这个范围不是JVM能够识别的,此时就需要自己定义异常类。

语法格式:

public class 自定义异常类名 extends Exception{}

使用继承 Exception 类的类定义自定义异常逻辑类,而 Exception 中常用的构造方法也可以被子类用super调用。同样还可以使用继承自 runtimeException 类的类实现运行时的自定义异常类。

示例:

//栈操作异常:自定义异常!public class StackOperationException extends Exception{ // 编译时异常!    public MyStackOperationException(){    }    public MyStackOperationException(String s){        super(s);    }}

10 Java编程基础教程系列

【Java编程进阶】封装继承多态详解

【Java编程进阶】抽象类和接口详解

【Java编程进阶】Object类及常用方法详解