Bootstrap

Java异常超详细讲解(超简单理解)

7000字的文章,实属不易,如果看着合适请动动小手点点关注,点点点赞,点点收藏吧,谢谢!

不喜勿喷,谢谢!

使用工具 IntelliJ IDEA Community Edition 2023.1.4

 使用语言 Java8/JDK1.8

目录

1.初识异常

1.1 异常概念

1.2 计算机两大杀手

1.3 使用if-else替代异常的缺点

2.异常处理机制

2.1 异常处理结构

2.1.1 异常处理结构语法

2.1.2 异常格式的常见组合

2.2 在程序中处理异常

2.2.1 使用try-catch语句处理异常

2.2.2 2.使用try-catch-finally语句处理异常

2.3 多重catch语句

 2.4 异常分类及处理流程

2.4.1 Error类和Exception类

2.4.1.1 Error类

2.4.1.2 Exception类

2.4.1.2.1 运行时异常

2.4.1.2.2 Checked异常

2.4.2 Java异常处理流程总结

2.4.3 声明异常——throws关键字

2.4.3.1 throws语法

2.4.3.2 调用声明异常方法的处理方式(不处理会报错) 

2.4.3.3 声明异常的好处

2.4.4 抛出异常——throw关键字 

2.4.5 自定义异常

2.4.5.1 自定义异常的三个步骤

2.4.5.2 throw关键字的应用场景


1.初识异常

1.1 异常概念

在生活中,异常情况随身都有可能发生。下面将从生活中的异常过渡到程序,帮助大家建立对异常的初步认识。

在日常生活中,几乎随时随处可能出现意外情况。以学校生活为例,在上学的路上发生交通事故或拥堵、教员生病、教室投影不能正常使用、机房网络感染病毒等,这些都是意外情况。虽然这些意外情况是偶然事件,但是一旦发生就会带来不小的麻烦,影响正常的学习生活。同理,在程序中也会发生类似的情况,下面看看程序中的异常。

在程序开发过程中, 程序员虽然会尽量避免错误的发生,但是总会遇到一些不可预期的问题,如除法运算时除数为0、数组下标越界、数据类型不一致、内存不足、栈溢出等,这些就是异常。

1.2 计算机两大杀手

在计算机的发展中有两大计算"杀手",一个是断点,另外一个是除数为0.因为除数在为0在数学的解是无穷大,对于计算机来说,如果是无穷大,则意味着内存将全部占满,所以,在程序开发中,务必避免除数为0的情况。

1.3 使用if-else替代异常的缺点

  1. 无法穷尽所有的异常情况(因为受程序员知识的限制,异常情况总比可以考虑到的情况多,总会有"漏网之鱼",所以程序总不够健壮);
  2. 影响程序可读性,维护难度高(这种错误处理和业务逻辑混杂的代码严重影响程序的可读性,增加程序维护的难度。

2.异常处理机制

在程序设计时,必须考虑到可能发生的异常事件并进行相应的处理,这样才能保证程序正常运行。

Java的异常处理机制也秉承着面向对象的基本思想,在Java中,所有的异常都定义为类。除了内置的异常类,Java也可以自定义异常类。此外,Java的异常处理机制也允许自行抛出异常。

2.1 异常处理结构

2.1.1 异常处理结构语法

Java针对异常的处理提供了try、catch、finally、throws。throw五个核心关键字,其中前三个关键字就可以组成常用的异常处理结构,语法如下:

try{
  //有可能出现异常的语句
}
[catch(异常类型 异常对象){
  //异常处理语句
}]
[finally{
  //一定会允许到的语句
}]

其中,try语句用于监听,将可能抛出异常的代码放在try语句块内,当try语句块内发生异常时,异常就会被抛出;catch语句用于捕获异常,catch语句用来捕获try语句块中抛出的异常;finally语句块总会被执行,主要用于回收try语句块内打开的资源,如数据库连接,网络连接和磁盘文件。

2.1.2 异常格式的常见组合

以上个事中的catch语句、finally语句都可选。实际上,这并不是表示catch语句、finally语句可以同时消失。异常格式的常见组合有try-catch、try-catch-finally、try-finally三种。        

2.2 在程序中处理异常

2.2.1 使用try-catch语句处理异常

以上程序使用了异常处理语句,当程序出现异常时,异常会被try语句监听到,然后被JVM(Java虚拟机)抛出,被catch语句捕获进行处理,执行catch语句内的异常处理代码,不再需要程序员自行编写if语句进行判断,简化了代码。

使用Java异常处理机制的目的是帮助程序员发现异常并解决异常。以上代码中采用输出错误提示的发生处理异常,不能明确地描述异常类型,不能精确定位问题所在。更好的方法是使用异常类中提供的printStackTrace()方法进行异常信息但完整输出。

捕获异常中的异常对象.printStackTrace();即可进行异常信息的完整输出。

从上述代码可以看出,Java异常处理机制可以自动捕获不同类型的异常。如果try语句块在执行过程中遇到异常,则异常处之后的代码将不再被执行,系统会将异常信息封装成相应类型的异常对象,包含异常的类型、异常出现时程序的运行状态及对该异常的详细描述。如果这个异常对象与catch语句中声明的异常类型相匹配,则会被自动捕获,Java异常处理机制把该异常对象赋给catch关键字后的异常参数,执行catch语句块中的语句。Exception类型的常用方法如下:

void printStackTrace()

输出异常堆栈信息。堆栈信息中包含程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用序列。

String getMessage()

返回异常的详细信息。该信息描述异常产生的原因,是printStackTrace()方法输出信息的一部分。

因为使用printStackTrace()方法输出的异常信息是最完整的,所以后续会使用该方法进行异常信息输出。在其输出结果中,可以自下向上观察程序的运行轨迹,最终定位到异常发生的位置。

2.2.2 2.使用try-catch-finally语句处理异常

如果程序出现异常,程序将自动跳转到catch语句块处理异常,后续输出语句将不再被执行。如果需要继续执行后面的语句,则需要添加finally语句。

finally语句块中的代码,无论是否出现异常,最终都会执行finally语句块的代码。

即使在try语句块或catch语句块中添加了return语句,finally语句块也会被正常执行。

那么,是否有finally语句块不被执行的情况呢?

答:在异常处理代码中执行System.exit(1),将退出JVM。

2.3 多重catch语句

在之前的案例中,程序出现的全部异常由统一的处理方式。但在实际开发中,有时会希望针对不同的异常类型采取不同的处理方式,这样就需要使用多重catch语句进行异常处理。语法如下:

try{
  //有可能出现异常的语句
}
[catch(异常类型1 异常对象){
  //异常处理语句1
}]
[catch(异常类型2 异常对象){
  //异常处理语句2
}]
[finally{
  //一定会允许到的语句
}]

算数异常(ArthmeticException)
输入类型不匹配异常(InputMismatchException)

  • Exception是所有异常类型的父类,所以能够被该catch语句全部捕获,执行相同的异常处理程序。如果需要针对可能发生的异常类型采取不同的处理方式,则需要使用多个catch语句块分别捕获不同的异常类型,输出不同的提示信息。
  • 最后一个catch语句使用Exception类型作为参数,因为它是所有异常类型的父类,可以处理前面的catch语句未匹配的异常。
  • 这样,当在try块中出现异常时,所封装的异常对象就会从多重catch语句所提供的参数类型中找到能够匹配的一个,执行其中的异常处理代码。
  • 当try语句块中发生异常时,系统将会按照从上到下的顺序依次检测每个catch语句,当匹配到某条catch语句后,后续其他catch语句块将不再被执行。
  • 以Exception作为参数的catch语句必须放在最后的位置,否则所有的异常都会被其捕获,后面以其子类异常作为参数的catch语句将得不到被执行的机会。

 2.4 异常分类及处理流程

前面已经介绍了异常处理及其对程序正常执行的重要性。为了能更好地理解异常的继承结构,必须理解异常的继承结构及处理流程。

所有的异常类型都继承Java.lang.Throwable类,它有两个重要的子类,分别是Error类和Exception类。

2.4.1 Error类和Exception类

2.4.1.1 Error类

Java.lang.Error类包含仅靠程序本身无法恢复的严重错误,是Java运行环境的内部错误或硬件问题,如内存资源不足、JVM错误等。应用程序不应该抛出这种类型的对象(一般由JVM抛出)。假如出现这种错误,除尽力使程序安全退出外,其他方法是无能为力的。因此在程序设计时,应该更关注Exception类。

2.4.1.2 Exception类

Java.lang.Exception类是程序本身可以处理的异常,可分为运行时(RunTimeException)异常与检查(Checked)异常。

2.4.1.2.1 运行时异常

可以在程序中避免的异常。这类异常在编译代码时不会被编译器检测出来,可以正常编译运行,但当程序进行时发生异常,会输出异常堆栈信息并中止程序执行,程序员可以根据需要使用try-catch语句捕获这类异常,如空指针异常、类型转换异常、数组越界异常等,这些异常包括Java.long.RuntimeException类及其子类,通过这些具体的异常类型,能够判断程序的问题所在。Java程序中常见的运行时异常如下:

常见异常
    ArithmeticException
        当出现算数错误时,抛出此异常。例如,在一个整数"除以0"时,抛出此异常
    ArrayIndexOutOfBoundsException
        当非法索引访问数组时,抛出此异常。例如,索引为负或大于等于数组长度
    ClassCastException
        当试图将对象强制转换为非本对象类型的子类时,抛出此异常
    IllegalArgumentException
        当向方法传递了一个不合法或不正确的参数时,抛出此异常
    InputMismatchException
        当欲得到的数据类型和实际输入的类型不匹配时,抛出此异常
    NullPointerException
        当应用程序试图在需要对象的地方使用null时,抛出此异常
    NumberFormatException
        当试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出此异常。例如,把"ABC"转换为数字

2.4.1.2.2 Checked异常

除运行时的异常,是由用户错误或问题引起的异常,这是程序员无法遇见的。在编译时,编译器会提示这类异常需要捕获。如果不进行捕获,则会出现编译错误。常见的编译异常由FileNotFoundException(Java字节输入流/Java字节输出流)异常、SQLException(Java连接数据库)异常等。

FileNotFoundException异常和IOException异常为Checked异常,必须进行异常处理才能通过编译。

在Java中进行异常类子类命名时都会使用XXXError或XXXException的形式,这样也是为了从名称上帮助开发人员进行区分。

2.4.2 Java异常处理流程总结

  1. 生成异常对象。如果程序在运行规程中出现异常,则JVM自动根据异常的类型实例化一个与之类型匹配的异常对象。如果发生异常的语句存在于try语句块中,则去匹配对应的catch语句;否则由JVM进行默认处理,输出异常现象后中断执行。
  2. 执行catch语句块,将所生成的异常对象匹配,则执行该catch语句块进行异常处理;否则,继续向下匹配其他catch语句块。如果无匹配的catch语句块·,则此异常对象交由JVM进行默认处理,输出异常信息。
  3. 执行finally语句块。无论最终是否有匹配的 catch语句块,都会执行finally语句块;如果没有finally语句块,则忽略。

整个过程类似于方法传参,所生成的异常对象根据catch语句后面的参数进行匹配,依据Java面向对象中向上转型的原理,所有异常对象都可以向其父类进行类型转换,所以所有的异常对象都可以使用Exception对象来接收。当无需对异常进行细分处理时,就可以简单地实现异常处理;否则,就需要使用多重catch语句对每类异常分别处理。 

2.4.3 声明异常——throws关键字

如果根据程序的设计,不适合立即去处理异常,而应声明可能发生异常,交由上一级(声明的异常)处理,此时,就需要使用throws关键字。

2.4.3.1 throws语法

throws关键字是方法可能抛出异常的声明,用在所声明的方法时,表示该方法可能会抛出此类异常。语法如下:

public void 方法名() throws 异常类型[,异常类型]{
  //方法体
}

throws关键字可以在一个方法定义处声明一类或多类异常,多类异常间使用逗号分隔。

2.4.3.2 调用声明异常方法的处理方式(不处理会报错) 

声明异常后调用者有以下两种处理方式。

  1. 通过try-catch语句处理异常
  2. 通过throws语句继续声明异常,交由上一级处理,如果main()方法继续声明异常,则交由上一级调用者,在main()方法中可能不进行异常处理,而由JVM处理,如下所示

public static void main(String[] args) throws Exception{
  //声明异常由JVM处理
}

throws 关键字用于在方法声明中指定该方法可能抛出的异常。当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。 

从实际开发的角度来讲,main()方法不建议声明异常,因为如果程序出现了错误,会导致程序中断执行。

2.4.3.3 声明异常的好处

用这个的好处是如果有多个方法都可以用一个try语句来进行异常处理然后抛出异常(在后面的Java多线程中有用)。

  1. 在异常方法里面声明对应的可能发生异常就行。
  2. 然后在try语句可以使用多重catch来进行异常检测之后找到对应的然后抛出catch的语句块。
  3. 如果声明多个异常只要对应其中一个就会走进去,找到对应的catch然后抛出对应的catch语句块。
  4. 可以把Excetion放在最后一个catch语句中进行异常处理,可以避免意料之外的异常。

2.4.4 抛出异常——throw关键字 

使用try-catch-finally语句进行异常处理,在这个异常处理结构中,由JVM进行判断并抛出异常信息,也就是程序遇到异常时会自动处理。但是有时可能会出现如下状况:

  1. 需要根据程序逻辑自定义异常类,这些异常类在Java异常体系中并未提供,不能抛出。例如,当程序中的注册流程执行时发生错误,需要抛出自定义的注册异常精准定位问题所在,但是注册异常在Java异常体系中不存在,就不能被自动捕获。
  2. 根据业务需要自行选择异常抛出时或自定义异常处理逻辑。例如,根据不同的逻辑判断条件而抛出异常。

在以上情况中,就需要使用throw关键字自行手动抛出异常,由调用者处理抛出异常的时机和异常的处理逻辑。由于使用throw关键字抛出的异常是异常类的对象,所以可以使用new关键字创建异常类的对象,通过throw关键字抛出,语法如下:

throw new 异常名([参数列表]); 

使用throw关键字抛出的只能是Throwable类或其子类的对象,下面的语句是错误的
throw new String("exception");
因为String类不是Throwable类的子类

上面的代码并不难理解,因为当异常发生时一定会由系统产生一个异常类的实例化对象,只是此时异常类的实例化对象是通过手动创建并使用throw关键字抛出的而已。

2.4.5 自定义异常

使用自定义异常时最后需要在对应的catch语句块中点打印异常信息方法(printStackTrace);

或者用throws关键字吧可能发生的异常声明为异常的父类Exception,这样就可以使用Exception直接调用打印方法,打印的是对应异常的方法

2.4.5.1 自定义异常的三个步骤

当Java异常体系中提供的异常类型不能满足程序的需要时,可以自定义异常类,使用自定义异常一般有三个步骤:

  • 定义异常类,继承Exception类或RuntimeException类;
  • 编写异常类的构造方法,并继承父类的实现。常见的构造方法有四种形式,可根据需求选择添加实现。构造方法如下所示;

  • 实例化自定义异常对象,并使用throw关键字抛出。
2.4.5.2 throw关键字的应用场景

使用throw关键字可以抛出自定义异常,那么自定义异常有哪些应用场景呢? 

  1. 项目开发一般由团队成员共同完成,为统一对外异常展示的方式,可以使用自定义异常。
  2. 项目中因业务逻辑错误需要抛出异常,但一般是符合Java语法的,所以在Java中不会存在这类异常。例如,年龄异常、性别异常等。
  3. 使用自定义异常可以隐藏底层异常,精确定位,异常信息更直观。自定义异常可以抛出明确的异常信息,根据异常名也可以区分其发生位置,方便进行程序的修改。 

;