Java基础:07.Java异常
一、异常概述及分类
开发过程中系统运行时会遇到一些问题,很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。在Java语言中,将程序执行中发生的不正常情况称为异常(开发过程中的语法错误和逻辑错误不是异常)。为了防止异常出现,我们应该提前进行异常的处理,而不是在终端直接呈现为报错,当异常出现后,程序员应该去修改代码减少异常的发生,处理异常只是一道保险而已。
1 |
|
Java程序在执行过程中所发生的异常事件广义上可分为两类:
- 错误(Error): Java虚拟机无法解决的严重问题,一般不编写针对性的代码进行处理,而是修改代码。如:JVM系统内部错误、资源耗尽等严重情况
1 |
|
异常(Exception):狭义上的异常是指其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界等。异常又可以分为:
编译时异常:是指编译器要求必须处置的异常,编译不会通过。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时的异常,对于这类异常,如果程序不处理,程序无法正常运行(编译不通过)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// ******************以下是编译时异常***************************
// 代码编译时会提示:Unhandled exception: java.io.FileNotFoundException、Unhandled exception: java.io.IOException
@Test
public void test01() {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}运行时异常:是指编译器不要求强制处置的异常,即使写了,编译也会通过。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。
java.lang.RuntimeException
类及它的子类都是运行时异常。对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。(这里需要注意平常在IDEA里运行的时候,运行时异常会打印一个异常信息,这就是运行的结果,即它是编译通过的,在异常发生之前的程序部分不会受到影响。默认的异常处理器,是打印异常信息并终止程序,所以异常信息会被打印,且之后的程序不会执行)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
41
42
43
44
45
46
47
48
49
50
51
52
53// ******************以下是运行时异常***************************
// 为了显示异常,以下代码编写时直接触发异常条件,编译不通过。真实环境中往往是可以编译通过,但因为运行时某些值动态改变而发生异常,导致程序终止
// NullPointerException,空指针异常
// java.lang.NullPointerException: Cannot invoke "String.charAt(int)" because "str" is null
@Test
public void test02(){
String str = null;
System.out.println(str.charAt(0));
}
// ArrayIndexOutOfBoundsException,数组越界异常
// java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 10
@Test
public void test03(){
int[] arr = new int[10];
System.out.println(arr[10]);
}
// ClassCaseException,类型转换异常
// java.lang.ClassCastException: class java.util.Date cannot be cast to class java.lang.String
@Test
public void test04(){
Object obj = new Date();
String str = (String) obj;
}
// NumberFormatException,数字格式化异常
// java.lang.NumberFormatException: For input string: "abc"
@Test
public void test05(){
String str = "abc";
int num = Integer.parseInt(str);
}
// InputMismatchException,输入类型异常
// 输入的如果不是整型会报错
@Test
public void test06(){
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();
System.out.printf("你输入的是:",score);
scanner.close();
}
// ArithmeticException
// java.lang.ArithmeticException: / by zero
@Test
public void test07() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
二、异常处理机制
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
1. try-catch-finally
1.1 语法
1 |
|
try语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象并将此对象抛出,一旦抛出对象以后,其后的代码就不再执行。
在catch语句块中是对括号中的异常对象进行处理的代码。一旦处理完成,就跳出当前的 try-catch-finally结构,继续执行其后的代码。
finally语句为异常处理提供一个统一的出口,其中的语句无论什么情况都会被执行,finally语句是可选的。
基本使用:
1 |
|
出现数值转换异常
总会被执行
hello—-2
1.2 注意事项
try语句后面必须加上catch语句或者finally语句,不可以单独存在。try-catch和try-catch-finally结构更常用。
在try结构中声明的变量,在出了try结构以后,就不能再被调用
每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象,捕获到哪个异常就去执行对应处理异常的代码
catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则会报错,因为父类在子类上面的话,都将进入父类的处理语句中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Test
public void test09(){
String str = "abc";
try{
int num = Integer.parseInt(str);
System.out.println("hello-----1");
}catch(NumberFormatException e){
System.out.println("出现数值转换异常");
}catch(NullPointerException e){
System.out.println("出现空指针异常"); //NullPointerException和NumberFormatException无继承关系,顺序无所谓
}catch(Exception e){ //Exception是父类,只能出现在下面,否则会报错
System.out.println("出现异常");
}
finally{
//System.out.println(num); //报错
System.out.println("总会被执行");
}
//System.out.println(num); //报错
System.out.println("hello----2");
}出现数值转换异常
总会被执行
hello—-2经常需要捕获异常的有关信息,去访问异常对象的成员变量或调用它的方法,常用的方法为:
getMessage() 获取异常信息,返回字符串
1
2
3
4
5
6
7
8
9@Test
public void test10(){
String str = "abc";
try{
int num = Integer.parseInt(str);
}catch(NumberFormatException e){
System.out.println(e.getMessage());
}
}For input string: “abc”
printStackTrace() 获取打印异常类名和异常信息,以及异常出现在程序中的位置。无返回值,更常用
1
2
3
4
5
6
7
8
9@Test
public void test11(){
String str = "abc";
try{
int num = Integer.parseInt(str);
}catch(NumberFormatException e){
e.printStackTrace();
}
}java.lang.NumberFormatException: For input string: “abc”
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:668)
at java.base/java.lang.Integer.parseInt(Integer.java:786)
…….
try-catch处理异常后,是会继续执行其后代码的,为什么还需要finally呢,因为涉及到catch代码中可能也会有异常,或者try-catch语句中有返回值的情况,这些情况想要执行一段在后面必须执行的代码,就得靠finally结构
- catch语句中存在异常的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Test
public void test12() {
try {
int a = 10;
int b = 0;
System.out.println(a / b);
} catch (ArithmeticException e) {
int[] arr = new int[10];
System.out.println(arr[10]); //数组越界异常
} catch (Exception e) {
//catch的异常并不会进入到这里进行处理,事实上这段代码中没有语句对其进行处理。catch的异常还是会被编译器报出。
System.out.println("123");
} finally {
System.out.println("总会被执行");
}
}总会被执行
java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 10
at Exception_laarn.ExceptionTest.test12(ExceptionTest.java:88)
…….
- try-catch-finally语句中存在返回值的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19@Test
public void test13() {
int num = method();
System.out.println(num);
}
public int method() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} finally {
System.out.println("总会被执行");
//由于finally中的语句总会被执行,如果没有finally语句则返回2,现在是返回3
return 3;
}
}我一定会被执行
3像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就一般声明在finally中
try-catch-finally结构可以嵌套
如果是捕捉IO输入输出流中的异常,一定要在try{…}catch{…}后,加finally{…}把输入输出流关闭
2. throws
2.1 throws基础用法
直接抛出异常是Java中处理异常的第二种方式。如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理
在方法声明中后加上throws语句可以声明抛出异常的列表,用逗号隔开,throws后面的异常类型可以是方法中可能产生的异常类型,也可以是它的父类。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象符合throws后的异常类型时,就会被抛出,异常代码后续的代码,不会再执行。
务必注意:throws表示出现异常的一种可能性,并不一定会发生这些异常
1 |
|
java.lang.ArithmeticException: / by zero
……
注意事项:
try-catch-finally的结构是真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
抛出的异常要是方法中会产生的异常类型,也可以是它的父类,如果异常类型不对应是没有意义的,编译器还是会抛出正确的异常类型
1
2
3
4
5
6
7//抛出不对应的异常类型没有意义
@Test
public void test15() throws ArrayIndexOutOfBoundsException{
int a = 10;
int b = 0;
System.out.println(a / b);
}java.lang.ArithmeticException: / by zero
……
2.2 重写方法声明抛出异常的原则
方法重写时如果也要抛出异常,子类重写的方法抛出的异常类型要求不能大于父类被重写的方法抛出的异常类型
1 |
|
3. try-catch 和throws的选择
体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。而是尽量自己写代码避免这类异常的出现
体会3:开发中如何选择使用try-catch-finally 还是使用throws?
3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
4.手动抛出异常throw
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
- 首先要生成异常类对象,然后通过
throw
语句实现抛出操作(提交给Java运行环境),注意不是throws!!! - 可以抛出的异常必须是Throwable或其子类的实例。
1 |
|
5. throw与throws的比较
throws出现在方法函数头;
而throw出现在函数体。
throws表示出现异常的一种可能性,并不一定会发生这些异常;
throw则是就是这句代码出抛出了异常,执行throw则一定抛出了某种异常对象。
throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
throw是具体向外抛异常的动作,所以它是抛出一个异常实例。两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明(尽管不加也可以),然后交给调用它的上层函数进行处理
6. 用户自定义异常类
偶尔会自定义异常类,但一般地,用户自定义异常类都是RuntimeException的子类。
自定义异常类需要(编写时可以照着已有的异常类模仿):
- 通常需要编写几个重载的构造器。
- 需要提供serialVersionUID
- 需要通过throw抛出。
- 规范命名。自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
1 |
|