异常 | 我的日常分享

异常

异常

一、什么是异常

概念:程序在运行过程中出现的不正常现象。出现异常不处理将终止程序运行。

异常处理的必要性:任何程序都可能存在大量的未知问题、错误;如果不对这些问题进行正确的处理,则可能导致程序的中断,造成不必要的损失。

异常处理:java编程语言使用异常处理机制为程序提供了异常处理的能力。

二、异常的分类

Throwable:可抛出的,一切错误或异常的父类,位于java.lang包中。

异常的两大类:

  1. Error:JVM、硬件、执行逻辑错误,不能手动处理。
    • StackOverflowError:栈内存溢出
    • OutOfMemoryError:内存溢出
  2. Exception:程序在运行和配置中产生的问题,可处理。
    • RuntimeException:运行时异常,可处理,可不处理。
    • CheckedException:检查时异常,必须处理。实际不存在此类,Exception子类中除了RuntimeException外其它子类都是检查时异常。

2.1 常见的运行时异常

类型 说明
NullPointerException 空指针异常
ArrayIndexOutOfBoundsException 数组越界异常
ClassCastException 类型转换异常
NumberFormatException 数字格式转换异常
ArithmeticException 算术异常

RuntimeException以及其所有子类都是运行时异常。RuntimeExceptionException异常的子类。

image-20220314095027465

Exception类中除了RuntimeException,其它子类都是检查时异常。

image-20220314095315583

2.2 演示常见运行时异常

  1. 运行时异常:

    1
    2
    3
    //1、空指针异常 java.lang.NullPointerException
    String name = null;
    System.out.println(name.equals("aaa"));

    image-20220314100239778

  2. 数组越界异常

    1
    2
    3
    //2、数组越界异常 java.lang.ArrayIndexOutOfBoundsException
    int[] arr = {10,20,30};
    System.out.println(arr[3]);

    image-20220314100415702

  3. 类转换异常

    1
    2
    3
    //3、格式转换异常 java.lang.ClassCastException
    Object str = "hello";
    Integer i = (Integer) str;

    image-20220314100827981

  4. 数字格式异常

    1
    2
    //4、数字格式异常 java.lang.NumberFormatException
    int n = Integer.parseInt("100a");

    image-20220314102121395

  5. 算术异常

    1
    2
    //5、算术异常 
    int num = 10/0;

    image-20220314102259292

2.3异常的产生与传递

1、异常的产生

当程序员在运行时遇到不符合规范的代码或结果时,会产生异常或程序员使用throw关键字手动抛出异常。

1、自动抛出异常:当程序在运行时遇到不符合规范的代码或结果时,会产生异常。

2、手动抛出异常:程序员使用throw抛出异常,语法throw new 异常类型(参数)

3、产生异常结果:相当于遇到return语句,导致程序因异常而终止。

2、异常的传递

按照方法的调用链反向传递,如始终没有处理异常,最终会由JVM进行默认异常处理(打印堆栈跟踪信息)。

1、检查时异常(受检查异常):throws声明异常,修饰在方法参数列表后端。

2、运行时异常(不受检查异常):因可处理可不处理,无需声明异常。

案例1:异常的产生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test2 {
public static void main(String[] args) {
/*
演示异常的产生
输入两个数字实现两数的相除
*/
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
int result = n1 / n2;
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

  1. 当输入15 3,程序正确计算出结果,并正常结束。

    image-20220314155343215

  2. 当输入15 0,当执行到int result = n1 / n2;程序抛出异常,我们并没有处理,程序异常结束。

    image-20220314155505344

  3. 当输入 15 a,当执行到int n2 = scanner.nextInt();程序抛出异常,异常结束。

    image-20220314155709435

案例2:异常的传递

将案例1的代码分为两个函数执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test3 {
public static void main(String[] args) {
/*
演示异常的传递
输入两个数字实现两数的相除
*/
operation();
}
public static void operation(){
divide();
}
public static void divide(){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
int result = n1 / n2;
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

  1. 当输入15 0时,可以看到抛出的异常是按照方法的调用顺序,反方向向上传递。divide函数交给operation,operation交给main,前面两个函数没有对异常做处理,最终由JVM进行默认异常处理,也就是打印运行结果中的堆栈跟踪信息。

    image-20220314160256673

  2. 当输入15 a时,

    image-20220314160616719

三、异常的处理

  • Java的异常处理是通过5个关键字来实现的:
    • try:执行可能产生异常的代码
    • catch:捕获异常,并处理
    • finally:无论是否发生异常,代码总能执行
    • throw:手动抛出异常
    • throws:声明方法可能要抛出的各种异常

语法:

1
2
3
4
5
6
7
try{
//可能出现异常的代码
}catch(Exception e){
//异常处理的相关代码,如getMessage()、printStackTrace()
}finally{
//无论是否出现异常,都需执行的代码结构
}

3.1 异常处理 try…catch

1
2
3
4
5
try{

}catch(Exception e){

}

会发生三种情况:

  1. 正常,没有发生异常
  2. 出现异常并处理
  3. 异常类型与捕获的异常不匹配

案例1:try…catch的使用

将可能发生异常的语句书写在try当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test4 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}
  1. 没有发生异常,与没写try…catch效果一致。

    image-20220315133527563

  2. 虽然发生了异常,但是程序执行完毕正常输出,程序正常退出。

    image-20220315133622134

案例2:try…catch捕获的异常与实际发生的不符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test5 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} catch (NullPointerException e) {
e.printStackTrace();
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

发生了异常,不是空指针异常,未被捕获,程序异常退出。

image-20220315134124806

案例3:使用自定义打印异常信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test6 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} catch (Exception e) {
//e.printStackTrace();
System.out.println("异常信息:"+e.getMessage());
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

没有打印红红的堆栈信息了。

image-20220315134435100

3.2 异常处理try…catch…finally

语法:

1
2
3
4
5
6
7
try{
// 可能出现异常的代码
}catch{
// 异常处理的相关代码,如:getMessage() printStackTrace()
}finally{
// 是否发生异常都会执行的代码,在这里释放资源等
}

1、finally内代码是否发生异常都会执行,可以用于释放资源。

2、finally内代码不执行的唯一情况,就是在此之前退出了java虚拟机。

案例1:try…catch…finally的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test7 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} catch (Exception e) {
//e.printStackTrace();
System.out.println("异常信息:"+e.getMessage());
}finally {
System.out.println("释放资源...");
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

1、发生异常,执行了释放资源...

image-20220315135334586

2、未发生异常,执行了释放资源...

image-20220315135455286

案例2:未正确捕获到异常,finally仍会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test8 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} catch (NullPointerException e) {
//e.printStackTrace();
System.out.println("异常信息:"+e.getMessage());
}finally {
System.out.println("释放资源...");
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

image-20220315140247321

案例3:finally不会执行的情况。

手动退出虚拟机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test8 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
// 手动退出java虚拟机
System.exit(0);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("异常信息:"+e.getMessage());
}finally {
System.out.println("释放资源...");
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

image-20220315135637394

3.3 异常处理 多重catch

语法:

1
2
3
4
5
6
7
8
9
try{
// 可能出现异常的代码
}catch(异常类型1){
// 满足异常类型1执行的相关代码
}catch(异常类型2){
// 满足异常类型2执行的相关代码
}catch(异常类型3){
// 满足异常类型3执行的相关代码
}

1、子类异常在前,父类异常在后;否则父类会将子类匹配到,子类永远无法匹配到。

2、发生异常时,按顺序逐个匹配。

3、只执行第一个与异常类型匹配的catch语句。

4、finally根据需要可写可不写。

案例1:多重catch的使用

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
public class Test10 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
String str = null;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
if (n1 != 1){
str = "abc";
}
str.equals("a");
} catch (ArithmeticException e){
System.out.println("算术异常...");
} catch (InputMismatchException e){
System.out.println("输入不匹配异常...");
} catch (NullPointerException e){
System.out.println("未知异常...");
}catch (Exception e) {
//e.printStackTrace();
System.out.println("异常信息:"+e.getMessage());
}finally {
System.out.println("释放资源...");
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

1、算术异常

image-20220315144812841

2、输入不匹配异常

image-20220315144836591

3、未知异常

image-20220315144753017

3.4 异常处理try…finally

  • try...finally不能捕获异常,仅仅用来当发生异常时,用来释放资源。
  • 一般用在底层代码,只释放资源不做异常处理,把异常向上抛出。

语法:

1
2
3
4
5
try{
// 可能出现异常的代码
}finally{
// 是否发生异常都会执行,可以释放资源等...
}

案例1:异常向上抛出给了main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test11 {
public static void main(String[] args) {
divide();
}
public static void divide(){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int result = 0;
String str = null;
try {
int n1 = scanner.nextInt();
System.out.println("请输入第二个数字");
int n2 = scanner.nextInt();
result = n1 / n2;
} finally {
System.out.println("释放资源...");
}
System.out.println("结果为:"+result);
System.out.println("程序执行完毕");
}
}

运行结果:

image-20220315145839069

改进:在main中捕获异常。

1
2
3
4
5
6
7
8
9
10
...
public static void main(String[] args) {
try {
divide();
} catch (Exception e) {
//e.printStackTrace();
System.out.println(e.getMessage());
}
}
...

运行结果:捕获到异常,正确退出。

image-20220315150008543

四、声明异常

如果在一个方法体中抛出了异常,如何通知调用者呢?

throws关键字:声明异常。

使用原则:底层代码向上声明或者抛出异常,最上层一定要处理异常,否则程序中断。

语法:在方法名后使用throws关键字声明异常。

作用:告诉表示此处可能会出现异常,告诉调用者要处理。

可抛出多个异常:

1
2
public void test() throws Exception,NullPointerException, InputMismatchException {
}

案例1:使用throws声明异常。

image-20220315151618722

运行结果:

必须对其捕获或向上声明

image-20220315151703366

案例2:案例1捕获异常

image-20220315151838070

案例3:案例1向上声明

这个相当于最终异常还是抛给了java虚拟机处理。

image-20220315151948303

五、抛出异常

除了系统自动抛出异常外,有些问题需要程序员自行抛出异常。

throw关键字:抛出异常。

语法:throw 异常对象

案例1:Person类设置年龄,不符合要求则抛出异常

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
38
39
40
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("小明");
p1.setAge(130);
System.out.println(p1);
}
}
class Person {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
if (age >= 0 && age <= 120){
this.age = age;
}else{
throw new RuntimeException("年龄不符合要求");
}
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

运行结果:

设置年龄130,不符合要求,抛出了运行时异常,提示信息为:年龄不符合要求。

image-20220315160210326

案例2:抛出检查时异常

当抛出检查时异常时,这时候需要处理异常,我们可以在这里使用try...catch捕获异常或throws声明异常。

image-20220315160608507

  1. 使用try...catch捕获异常

    image-20220315160749983

  2. 使用throws声明异常

    image-20220315160833474

    如果不处理则报错,

    image-20220315160940100

六、自定义异常

对于上面两个案例中,抛出的是RuntimeExceptionException,我们无法通过抛出的类名见名知意。

自定义异常:声明继承Exception或其子类的类。

AgeException.java

继承Exception或其子类都可以,只需要写构造方法即可,不需要其他的代码,自定义异常的目的就是更改抛出异常类的名字。

1
2
3
4
5
6
7
8
public class AgeException extends Exception{
public AgeException() {
}

public AgeException(String message) {
super(message);
}
}

Person.java

setAge方法

1
2
3
4
5
6
7
public void setAge(int age) throws Exception{
if (age >= 0 && age <= 120){
this.age = age;
}else{
throw new AgeException("年龄不符合要求");
}
}

运行结果:

这样我们就能够见名知意。

image-20220327223247456

七、重写声明异常的方法

带有异常声明的方法重写:

  1. 子类不能抛出比父类更多、更宽泛的检查时异常

案例1:

不能抛出比父类更宽泛的检查时异常。

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public void eat() throws FileNotFoundException{
System.out.println("父类eat...");
}
}
class Dog extends Animal{
@Override
public void eat() throws Exception {
System.out.println("子类eat...");
}
}

运行结果:

image-20220327224548115

案例2:子类可以抛出父类的子类检查时异常

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public void eat() throws Exception{
System.out.println("父类eat...");
}
}
class Dog extends Animal{
@Override
public void eat() throws FileNotFoundException{
System.out.println("子类eat...");
}
}

案例3:对于运行时异常,不存在以上规则

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
public void eat() throws NumberFormatException{
System.out.println("父类eat...");
}
}
class Dog extends Animal{
@Override
public void eat() throws RuntimeException{
System.out.println("子类eat...");
}
}

注意:对于运行时异常没有上面的规则,而对于检查时异常,子类方法不能抛出比父类更更多、更宽泛的异常。