1、什么是异常

异常(Exception)指程序运行过程中出现的不期而至的各种状况。如:非法传参、内存溢出、文件找不到、网络连接失败等。

  • (在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个异常对象。

2、异常的体系结构

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的父类。

在Java的API中已经定义了许多异常类。这些异常类分为错误(Error)和异常(Exception)两大类。

异常的体系结构

2.1、错误(Error)

  • Error类对象由Java虚拟机生成并抛出。
  • 编译时被忽略,运行时可能会报错(抛出Error)
  • Error通常是灾难性的致命错误,是程序无法控制和处理的,如
    • OutOfMemoryError
    • ThreadDeath
  • 这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

2.2、异常(Exception)

  • Exception是程序本身可以处理的异常,这种异常分运行时异常非运行时异常两大类。
  • 程序书写过程中应当尽可能地去处理这些异常。

(1)运行时异常

  • RuntimeException(运行时异常)是Exception的一个重要分支。如:

    • ArrayIndexOutOfBoundsException(数组下标越界)
    • NullPointerException(空指针异常)
    • ArithmeticException(算术异常)
    • MissingResourceException(丢失资源)
    • ClassNotFoundException(找不到类)
  • 运行时异常又称不检查性异常(Unchecked Exception)

  • 编译时被忽略,运行时可能会报错(抛出Exception)

  • 程序书写过程中程序员可以选择捕获处理,也可以不处理。

  • 这些异常一般是由程序逻辑错误引起的,程序员应该从逻辑角度尽可能避免这类异常的发生

(2)非运行时异常

  • 非运行时异常是RuntimeException以外的异常。 如:

    • IOException
    • SQLException
    • 用户自定义的异常

    **注:**一般情况下,我们不需要自定义异常。

  • 非运行时异常又称检查性异常(Checked Exception)

  • 从程序语法角度来讲,非运行时异常是必须进行处理的异常,如果不处理,编译时就报错(抛出Exception)

3、Java异常处理机制

在 Java 应用程序中,异常处理机制为:

  • 使用try-catch-finally捕获异常

  • 使用throw或throws抛出异常

示例程序:

1
2
3
4
5
6
7
8
9
package com.atangbiji;
public class Application {
public static void main(String[] args) {
int a = 1;
int b = 0;

System.out.println(a/b);
}
}

由于被除数不能为0,所以上述程序在运行时会抛出ArithmeticException异常。运行结果如下图所示:

算术异常

3.1、捕获异常

Java中使用try-catch-finally捕获异常。其中:

  • **try:**监控可能出现异常的代码块。
  • catch:若出现异常,则捕获相应的异常。其中的参数是想要捕获的异常类型,如:Throwable、Error、Exception、ArithmeticException 等。
  • **finally:**主要处理善后工作。无论监控代码块是否出现异常,都会执行finally中的代码。

执行流程:

  1. 我们将可能产生异常的代码放入try块当中,当try块中代码没有异常发生时,catch块中的内容不会被执行,而直接执行之后的代码。
  2. 当try块发生异常时,会产生一个异常对象且当该类型能与catch块中的异常类型正常匹配时,程序就会进入到catch块执行相应的处理逻辑,然后顺序执行下去。
  3. 当出现异常且无法正常匹配处理则程序中断运行。

try-catch-finally语句执行流程

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atangbiji;
public class Application {
public static void main(String[] args) {
int a = 1;
int b = 0;

try{
//监控代码块
System.out.println(a/b);
}catch (ArithmeticException e){
//捕获异常
System.out.println("捕获到程序出现的ArithmeticException异常,分母不能为0!");
}finally {
//处理善后工作
System.out.println("无论监控代码块是否出现异常,都会执行finally。");
}
}
}

运行结果:

捕获到程序出现的ArithmeticException异常,分母不能为0!
无论监控代码块是否出现异常,都会执行finally。

注:

  • 一个try语句必须至少有一个catch语句块或一个finally语句块。

  • 当异常处理的代码执行结束以后,不会回到try语句去执行尚未执行的代码。

  • 每个try语句块可以伴随多个catch语句,用于处理可能产生的不同类型的异常对象,且我们必须先处理特殊类型的异常,再处理一般类型的异常(如:先处理Error异常再处理Throwable异常)。

  • 同if-else if语句一样,无论try语句后面有多少个catch语句,程序只能捕获第一个出现的异常。

  • 无论异常是否发生,即使try和catch块中存在return语句,finally都必须要执行。

  • finally语句块只有在一种情况下是不会执行的,那就是在执行finally之前遇到了System.exit(0)结束程序运行。

  • 我们可以先选中代码块,然后使用“Ctrl+Alt+T”快捷键快速生成捕获异常的代码。

    捕获异常

3.2、抛出异常

Java中我们也可以使用throw或throws主动抛出异常。其中:

1、throw:

  • 用在方法体内。
  • 抛出的是一个异常类的对象。
  • 只能抛出一个异常对象。
  • 当执行到throw语句时,程序会立即停止。
  • 如果要捕捉throw抛出的异常,则必须使用try-catch语句块或者try-catch-finally语句。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.atangbiji;
public class Application {
public static void main(String[] args) {
int a = 1;
int b = 0;

try{
//监控代码块
test(1,0);
}catch (ArithmeticException e){
//捕获异常
System.out.println("捕获到程序出现的ArithmeticException异常,分母不能为0!");
}finally {
//处理善后工作
System.out.println("无论监控代码块是否出现异常,都会执行finally。");
}
}

public static void test(int a, int b){
if (b == 0){
//使用throw语句主动抛出异常
throw new ArithmeticException();
}
System.out.println(a/b);
}
}

运行结果:

捕获到程序出现的ArithmeticException异常,分母不能为0!
无论监控代码块是否出现异常,都会执行finally。

2、throws:

  • 用在方法的声明处(参数列表之后,方法体之前)。
  • 后面跟的是异常的类型。
  • 可以抛出多个不同类型的异常,使用逗号分隔。
  • 表示此处不处理异常,交由方法调用处处理,也就是向上抛出异常。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.atangbiji;
public class Application {
public static void main(String[] args) {
int a = 1;
int b = 0;

try{
//监控代码块
test(1,0);
}catch (ArithmeticException e){
//捕获异常
System.out.println("捕获到程序出现的ArithmeticException异常,分母不能为0!");
}finally {
//处理善后工作
System.out.println("无论监控代码块是否出现异常,都会执行finally。");
}
}

//使用throws语句主动抛出异常
public static void test(int a, int b) throws ArithmeticException{
System.out.println(a/b);
}
}

运行结果:

捕获到程序出现的ArithmeticException异常,分母不能为0!
无论监控代码块是否出现异常,都会执行finally。

注:

  • 使用throw主动抛出的异常,是必须要捕获的(Java内置的异常JVM可以帮我们捕获,自定义的异常我们必须自己捕获)。
  • 我们既可以在throw后立即捕获异常;也可以暂不处理并使用throws向上抛出异常,交由方法调用处捕获(欠的债迟早都是要还的)。

4、自定义异常(不常用)

Java中内置的异常类已经可以描述编程时可能出现的大部分异常。当然,用户也可以自定义异常。

用户自定义异常类,只需继承Exception类即可。

如:自定义一个输入非正整数的异常类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.atangbiji;
public class Application {
public static void main(String[] args) {
//使用tyr-catch捕获自定义异常
try {
input(-1);
} catch (MyException e) {
e.printStackTrace();
}
}

//使用throws向上抛出异常
public static void input(int a) throws MyException {
System.out.println("输入数字为:" + a);
if (a <= 0){
throw new MyException(a);//输入数字不是正整数时使用throw主动抛出异常
}
System.out.println("输入数字为正整数。");
}
}

//自定义的异常类
class MyException extends Exception{
//输入数字
private int inputValue;
//构造函数
public MyException(int inputNum) {
this.inputValue = inputNum;
}
//出现异常时的输出信息
@Override
public String toString() {
return "MyException{" +
"inputValue=" + inputValue +
'}' + "输入出现异常,输入数字不是正整数!";
}
}

由于输入的数字为 -1,所以运行结果为:

自定义异常

5、经验总结

  • 对于运行时异常,我们在采用合理的逻辑去规避它的同时,应使用try-catch语句进行处理。

  • 在多重catch块后面,我们可以加一个catch(Exception e),来处理可能被遗漏的异常。

  • 对于不确定的代码,我们也可以加上try-catch语句,来处理潜在的异常。

  • 尽可能地去处理异常,尽量不要只是简单地调用系统默认生成的e.printStackTrace()打印输出异常,进而避免程序中断。

  • 具体如何处理异常,要根据不同的业务需求和异常类型来决定。

  • 尽量添加finally语句块去释放占用的资源(如:IO流中的异常等)。

    (本讲完,系列博文持续更新中…… )

阿汤笔迹微信公众平台

关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2020/12/24/exception
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。