异常,就是不正常的意思,在程序中的意思是:在程序执行过程中,出现的非正常情况,最终会导致JVM的非正常停止
在Java等面向对象的编程语言当中,异常本身就是一个类,产生异常就是创建异常对象并抛出,Java(或者说JVM)处理异常的方式是中断处理
注意:异常指的不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行
异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable
,其下有两个子类java.lang.Error
和java.lang.Exception
,所有的异常都是由 Throwable
类继承而来的,为了更清晰表达他们的层次关系,下面给出一张图
其中 Error
类描述了Java运行时系统的内部错误与资源耗尽错误,这是我们攻城狮所不能处理、只能尽量避免的;而我们这里所谈论的异常,是指 Exception
类,这是由于使用不当导致,是可以避免的
在设计Java程序时,要重点关注 Exception
类,该类还可以分为两个分支, RuntimeException
与其他异常例如 IOException
等,一般的规则是:由编程错误导致的异常属于 RuntimeException
;如果程序本身没有问题,但由于I/O错误这类问题导致的异常属于其他异常
要注意的是,RuntimeException
可以处理也可以不处理,一般不处理;而其他异常例如 IOException
等则必须处理
关于异常的产生,我们来举个栗子,下面是演示程序代码
public class Demo {
public static void main(String[] args) {
int [] arr = {1, 2, 3};
int e = get(arr,3);
System.out.println(e);
}
private static int get(int[] arr,int index) {
int element = arr[index];
return element;
}
}
① main
方法调用了get
方法访问数组中索引为3的值,但数组的长度是3,索引是0-2,因此JVM就会检测出程序会出现异常情况,这时候JVM就会做两件事情:
JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的内容、原因与位置
new ArrayIndexOutOfBoundsException("Index 3 out of bounds for length 3");
在get
方法中,没有异常处理的逻辑(即try...catch
),那么JVM就会把异常对象抛出给该方法的调用者main
方法来处理这个异常
② main
方法收到了这个异常对象,但是main
方法也没有异常处理的逻辑,因此他会继续把异常对象抛出给main
方法的调用者JVM处理
③ JVM接收到了这个异常对象,做了两件事情:
上面简单举例介绍了异常的产生过程,那么怎么来处理这个异常呢,方法一为本小节所介绍的 throws
声明
首先来介绍一下throw
关键字(注意这个是throw
不是throws
)
throw
的作用是用于在方法中抛出指定的异常,一般只在自定义异常时使用,此处仅简单介绍以免引起混淆,详细使用方法将在自定义异常介绍
接下来就介绍throws
关键字
当成员方法内部抛出了一个异常的时候,我们必须把这个异常处理,那么我们可以使用throws
关键字处理异常对象,把该异常对象进行声明,他就会抛出给方法的调用者处理(说白了就是自己不处理,给别人处理),最终是交给JVM处理,也就是中断处理,他的基本格式如下:
有几个注意事项是需要关注的:
① throws
关键字必须写在方法声明处,即
修饰符 返回值类型 方法名(参数列表) throws XxxxException{}
② throws
关键字后声明的异常必须是Exception
或者是Exception
的子类
③ 方法内部如果出现了多个异常对象,那么throws
后也必须声明多个异常(如果抛出异常对象有父子关系,则只声明父类异常即可)
④ 如果调用了一个声明异常的方法,那么该方法必须继续对该异常进行处理,可以继续使用throws
声明,或使用下一小节的try...catch
自己处理异常
上一节介绍了处理异常的一种方法,这里介绍处理异常的另一种方法。前面说了使用throws
处理异常就是自己不处理,给别人处理,那么try...catch
则刚好相反,该种处理方式为自己处理异常
首先给出一个格式:
try{
可能会产生异常的代码
} catch(异常类名,变量名){
异常的处理逻辑,出现异常之后,怎样处理异常对象
一般在工作中会把该异常信息写入日志当中
}
下面说一下注意事项:
① try
中可能会抛出多个异常对象,那么就可以使用多个catch
来处理这些异常
② 如果try
中产生了异常,那么就会执行catch
中对应的异常处理逻辑,执行完catch
中的处理逻辑后,继续执行try...catch
后的代码
③ 如果try
中没有产生了异常,那么就不会执行catch
中对应的异常处理逻辑,执行完try
中的代码后,继续执行try...catch
后的代码
Throwable
是所有异常的父类,在该类中定义了3个异常处理的方法:
String getMessage()
:返回此异常的简短描述
String toString()
:返回此异常的详细描述
void printStackTrace()
:JVM打印该异常对象,默认此方法,打印的异常信息是最全面的
当存在多个异常的时候使用捕获(try...catch
)如何处理呢?有三种处理方法:.
① 多个异常分别处理
该种方法,说白了就是存在多少个异常就写多少个try...catch
,一种异常使用一组try...catch
来处理
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
List<Integer> list = List.of(1, 2, 3);
try{
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
try{
System.out.println(list.get(3));
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
② 多个异常一次捕获多次处理
该种方法,简单来说就是只写一个try
,把所有有可能出现异常的代码都放进去try
中,然后写若干个catch
,一个catch
对应一种异常
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
List<Integer> list = List.of(1, 2, 3);
try{
System.out.println(arr[3]);
System.out.println(list.get(3));
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}catch (IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
但是该方法有一个注意事项:
catch
中定义的异常变量,如果有父子类关系,那么子类的异常变量必须写在上边,否则会报错。其原理是,若出现异常,则会从catch
中顺序查找可以赋值的异常变量,倘若父类异常放在上边,则子类异常可以用多态的形式进入这个catch
,那么写在父类catch
后的子类catch
相当于没用,因此必须子类写在父类前
③ 多个异常一次捕获一次处理
该种方法,其形式同样是只有一组try...catch
,把所有可能产生异常的代码都放进try
中,然后catch
中的异常变量写一个无论try
中出现哪种异常都能接收的异常类型,即可以是try
中所出现异常类型的共同父类
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
List<Integer> list = List.of(1, 2, 3);
try{
System.out.println(arr[3]);
System.out.println(list.get(3));
}catch (Exception e){
System.out.println(e);
}
}
}
finally
代码块是跟try...catch
一起使用的,在finally
代码块中的代码,无论是否出现异常,他都会被执行。为什么需要这种操作呢?因为try...catch
的处理逻辑是,当try
中的某行代码检测出异常,那么该行代码的后续代码就不会被执行,直接跳转到catch
中,那么我又必须使用那行代码怎么办呢?这时候可以放进去finally
代码块中,下面举个例子
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try{
System.out.println(arr[3]);
System.out.println("资源释放");
}catch (Exception e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
假如我需要无论是否产生异常,都打印输出“资源释放”,那么可以把代码改成:
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try{
System.out.println(arr[3]);
}catch (Exception e){
System.out.println(e);
}finally {
System.out.println("资源释放");
}
System.out.println("后续代码");
}
}
需要值得注意的是:
① finally
代码块不能单独使用,必须和try...catch
一起使用
② finally
一般用于资源释放,无论程序是否异常,最后都要资源释放(IO)
③ 如果finally
中有return
语句,则永远返回finally
中的结果,应该避免发生该情况
下面来解释一下第三点,例如我的程序是这样子的:
public class Demo {
public static void main(String[] args) {
int a = getA();
System.out.println(a);
}
private static int getA() {
int a = 3;
try{
return a;
}catch (Exception e){
System.out.println(e);
}finally {
return 10;
}
}
}
显然这里我们想要的是不异常返回3,但由于finally
代码块中也有return
语句,因此无论有没有产生异常都会返回10
这里先简单给出子父类异常的使用规则:
给出简单的栗子:
public class Fu {
public void method01() throws NullPointerException,ClassCastException{}
public void method02() throws IndexOutOfBoundsException{}
public void method03() throws ArrayIndexOutOfBoundsException{}
public void method04() {}
}
public class Zi extends Fu{
public void method01() throws NullPointerException,ClassCastException{/*子类抛出与父类相同的异常*/}
public void method02() throws ArrayIndexOutOfBoundsException{/*子类可抛出父类异常的子类*/}
public void method03() {/*父类抛出异常,子类可以不抛出*/}
public void method04() {/*因父类没有抛出异常,此处若有异常只能try...catch处理,不能throws*/}
}
关于异常的种类,官方的JDK中给我们定义了很多异常类,但是,往往在项目开发中,这些异常类是不够用或者不能满足我们的设计要求的,这时候就需要自定义一些异常类以供使用,其定义的格式与定义子类格式类似:
public class XxxxException extends Exception或者RuntimeException{
//添加一个空参构造方法
//添加一个带异常参数构造方法
}
注意:
Exception
结尾,说明该类时一个异常类Exception
或RuntimeException
Exception
,那么该自定义异常类就是一个编译期异常,如果方法内部抛出了编译期异常,那么必须处理,要么throws
,要么try...catch
RuntimeException
,那么该自定义异常类就是一个运行期异常,无需处理,交给JVM处理(中断处理)查看ArrayIndexOutOfBoundsException
的源码我们可以发现,它的构造方法均调用了父类的构造方法,因此我们在自定义异常时也可以调用父类的构造方法,让父类来处理这个异常信息