Java-异常处理(5)

 
 

异常处理

程序错误.png
程序错误1.png

 

一、 Java异常基础

1.1 为什么要引入异常处理机制?

程序的错误分为:
编译错误:程序员编写程序时语法上出现的错误;
运行错误:程序员编写的程序在语法上没有错误,但是程序在运行时出现错误,本章就主要针对该类错误讲解 - - 即异常、异常类和异常处理机制。

 

1.2 以往的错误处理方法

**主要缺陷:**  程序复杂  可靠性差  返回信息有限  返回代码标准化困难  以往的程序开发过程中,常常采用**返回值**进行处理。例如,在编写一个方法,可以返回一个状态代码,调用者 根据状态代码判断出错与否。若状态代码表示一个错误,则调用该错误的处理程序进行相应的处理,或显示一 个错误页面或错误信息。

举例 以往的错误处理方法:采用返回值进行处理

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
举例:实现将一个文件从硬盘加载近来,导致加载可能失败的运行错误有硬盘错误、文件无法找到等
int status=loadTextfile();
If (status!=1){
//something unusual happened, describe it
switch(status) {
case 2:
//file not found
break;
case 3:
//disk error
default:
//other error}
}else {
//file loaded OK, continue with program}
```

 
 
#### <font color="#20B2AA" face="Comic sans MS">1.3 Java异常处理方法</font>
<table><tr><td bgcolor=#ADD8E6><font face="Comic sans MS">Java异常处理方法:Java为运行错误引入了异常、异常类和异常处理机制。
**异常:**特殊的运行错误,是在程序运行过程中发生的、会打断程序正常执行的错误
 例如:
 除0溢出
 文件找不到
数组元素下标越界 

**异常类:**Java用面向对象的方法处理异常,Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种
特定的运行错误,每一个异常事件由一个异常类的对象来代表。
*例如:除0溢出(ArithmeticException)
   文件找不到(FileNotFoundException)
   数组元素下标越界(ArrayIndexOutofBoundsException)*

**异常处理机制:抛出异常——捕捉异常:**
1. 当出现了一些错误,方法都会产生一个<font color="red">异常对象</font>,这个异常对象将交由运行系统来处理。此过程就称为抛出
<font color="red">(throwing)异常</font>。**包括:系统抛出、用户自定义抛出。**
2. 接着,运行系统开始寻找合适的处理方法,来处理这个异常。如果系统找到了一个适合的处理该异常方法,
这一过程就叫<font color="red">捕获异常</font>。
</font></td></tr></table>

&emsp;
&emsp;
#### <font color="#20B2AA" face="Comic sans MS">1.4 异常处理的语法支持</font>
<table><tr><td bgcolor=#ADD8E6><font face="Comic sans MS"> **-try,catch,throws,throw,finally**
(1) try包含可能出现异常的语句块;
(2) 一个或多个catch块紧随try{}块,每个catch块通常处理指定类型的异常;
(3) finally引导块紧随catch块后,主要用于清理现场(可有可无)。
格式:
try
{ ......
}catch( ExceptionName1 e )
{ ......}
catch( ExceptionName2 e )
{ ......}
finally
{ ......}
注意:finally总是执行,catch块不一定执行
</font></td></tr></table>

&emsp;
&emsp;
#### <font color="#20B2AA" face="Comic sans MS">1.5 总结</font>
<table><tr><td bgcolor=#EEE0E5><font face="Comic sans MS"> (1) Java的异常处理把错误集中起来统一处理。程序员只需要说明何处可能出现异常,如何处理即可;
(2) 采用面向对象的思想标准化了各种错误的类型;
(3) Java把程序运行过程中可能遇到的问题分为两类,一类是致命性的,即程序遇到了非常严重的不正常状
态,不能简单地恢复执行,这就是<font color="red">**错误(对应Error类)**</font>,如程序运行过程中内存耗尽。另一类是非致命性的,
通过某种处理后程序还能继续运行,这就是<font color="red">**异常(对应Exception类)**</font>。
</font></td></tr></table>


&emsp;
&emsp;
### <font color="#FF6347" face="Comic sans MS">二、 异常类的层次</font>
#### <font color="#20B2AA" face="Comic sans MS">2.1 异常分类</font>

<font face="Comic sans MS"><font color="red">**☕异常类:**</font>异常在Java中都是作为类的实例(对象)的形式出现的。
&emsp; 如 Throwable类, Exception类, Error类……

<font color="red">**☕Java中异常分类:**</font>
(1) <font color="#00B2EE">**Error类及其子类:**</font>描述Java运行时刻系统内部的错误或资源枯竭导致的错误,无法恢复和抛出,发生几率小;
(2) <font color="#00B2EE">**Exception类及其子类:**</font>普通程序可以从中恢复,分为<font color="red">**运行时异常**</font>和<font color="red">**非运行时异常**</font>。

&emsp;异常在Java中也是作为类的实例的形式出现的。Java中的所有的异常类都是从Throwable类派生出来的。<font color="red">**Throwable类有两个直接子类:**</font><font color="#00B2EE">**java.lang.Error**</font> 和 <font color="#00B2EE">**java.lang.Exception**</font>。


异常类的层次结构如下图所示。
![58.png](https://i.loli.net/2018/12/28/5c2621a200c43.png)
**(1) 🔺Error类及其子类主要用来描述一些Java运行时刻系统内部的错误或资源枯竭导致的错误。**普通的程序不能从这类错误中恢复,也无法抛出这种类型的错误,这类错误出现的几率是很小的。
**(2) 🔺另一个异常类的子类是Exception类和它的子类。**在编程中错误的处理主要是对这类错误的处理,如除数为零、数组下标越界等。类Exception是普通程序可以从中恢复的所有规范了的异常的父类。
</font>

&emsp;
&emsp;
#### <font color="#20B2AA" face="Comic sans MS">2.2 Exception类的子类</font>

<font face="Comic sans MS"><font color="red">**☕Exception类子类有两种:**</font>
**运行时异常**和**非运行时异常(一般异常)** &emsp;&emsp;&emsp; *区别???*
<font color="#00B2EE">**(1)运行时异常:**</font>RuntimeException类及其所有子类。 运行时异常是程序员编写程序不正确所导致的异常,理论上,程序员经过检查和测试可以查出这类错误。如**除数为零等,错误的强制类型转换、数组越界访问、空引用**。
<font color="#00B2EE">**(2)非运行时异常(一般异常):**</font>指可以由编译器在编译时检测到的、可能会发生在方法执行过程中的异常,如找不到指定的文件等,这不是程序本身的错误,如果这些异常情况没有发生,程序本身仍然是完好的。  
<font color="red">**注意:**</font>**编译器强制要求Java程序必须**<font color="#00B2EE">**捕获**</font>**或**<font color="#00B2EE">**声明抛出**</font>**所有非运行时异常,但对运行时异常不作要求。运行时异常编译可以通过但是运行时出现异常;非运行时异常编译的时候就通不过。**

*例如:格式不正确的URL、试图为一个不存在的类找到一个代表它的类的对象。除了runtimeexception及其子类以外,其他exception类的子类都是非运行时异常。*
</font>

```java
//运行时异常和非运行时异常区别举例
[例5-1] RuntimeExceptionDemo1.java
class RuntimeExceptionDemo1{
public static void main(String args[]){
int i=0;
System.out.println(2/0);
}}

该程序能编译通过,而在运行时,出现如下提示:
> javac RuntimeExceptionDemo1.java
> java RuntimeExceptionDemo1
Exception in thread "main" java.lang.ArithmeticException: / by zero at
RuntimeExceptionDemo1.main(RuntimeExceptionDemo1.java:4)
//这里的"ArithmeticException"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[]){
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
}
会出现如下的错误提示:
javac NonRuntimeExceptionDemo1.java
NonRuntimeExceptionDemo1.java:5: unreported exception java.io.FileNotFoundException; must
be caught or declared to be thrown
FileInputStream in=new FileInputStream("text.txt");
^
NonRuntimeExceptionDemo1.java:7: unreported exception java.io.IOException; mustbe
caught or declared to be thrown
while((s=in.read())!=-1) System.out.print(s);
^
NonRuntimeExceptionDemo1.java:8: unreported exception java.io.IOException; mustbe caught
or declared to be thrown
in.close();

&emsp;
☕对运行时异常的说明:由于运行时异常可能会出现在程序的任何地方,而且出现的可能性非常大,因而由程序本身去检测运行异常出现与否,将会使程序的开销过大,所以
运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置
建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程的希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误。
&emsp;

☕对运行时异常和非运行时异常的总结:

(1) 当出现java.lang.ArithmeticException运行时异常时,不需要用户在程序中对其进行处理,而直接由Java运行时系统进行处理;
(2) 对于非运行时异常,Java编译器对程序进行编译的时候,便指出用户需要①捕获该类异常或者②声明抛出。即对于非运行时异常,用户需要在程序中进行处理,否则编译时无法通过。

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
① 捕获该类异常
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[]){
try{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}catch(Exception e){…}
}
}


② 声明抛出
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[])throws Exception{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
}

&emsp;
&emsp;

2.3 对于运行时异常和非运行时异常的一些说明

1. ☕编译器和异常:编译器强制要求程序员捕获或声明抛出非运行时异常
到底为什么要这么做呢???
对于运行时异常,编译器不强制要求,但用户也可以自己去捕获
这个时候会出现什么效果呢???

2. ☕运行时系统和异常:
(1) 异常都有抛出的轨迹;
(2) 对于所有异常,某个方法产生的异常 ,如果没有被捕获,就会自动抛给方法的调用者(但是对非运行时异常一定要声明抛出),如果调用者还没有捕获,再抛给调用者的调用者,以此类推,直到main方法里发现还没有捕获,那么运行时系统就会来处理这个异常,把异常信息和异常的轨迹信息打印给用户。

3. ☕两点建议:
(1) 建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程者希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误;
(2) 建议对于非运行时异常,用户应该去捕获,以交代程序员的处理该错误的代码,实在不行,就声明抛出给方法的调用者。

3. ☕异常类常用的方法:
public Exception()
public Exception(String s)&emsp; :该参数一般表示该异常对应的错误的描述
public String toString()&emsp;:返回描述当前异常对象信息的字符串
public String getMessage()&emsp;:返回描述当前异常对象信息的详细信息。
public void printStackTrace()&emsp;:打印当前异常对象使用堆栈的轨迹。

5S.png

4DI6.png

&emsp;
&emsp;

三、 try-catch-finally异常处理

3.1 概括try-catch-finally语句

用户处理异常的三种方法:
(1)用户可以用try-catch-finally语句进行抛出捕获处理
(2)如果不想捕获和处理异常,可以通过throws语句声明要抛出的异常
(3) 用户可以定义自己的异常类,并用throw语句来抛出。

运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置。即:运行时异常:会输出到设备显示哪里存在错误,请更正;非运行时异常:编译报错时提示必须添加非运行时异常处理,比如加try{}…catch(类名 对象){}或者使用throws来抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
try-catch-finally语句对程序运行进行监控,捕获和处理异常通常形式: 
try{
调用可能产生异常的方法及其它java语句;
}
catch(异常类名1 异常对象名e){
异常处理语句块;
}
catch(异常类名2 异常对象名e){
异常处理语句块;
}
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
[例5-3]访问文本文件text.txt,并将其在屏幕上打印出来。
import java.io.*;
class TryCatchFinally
{
public static void main(String args[]){
try{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
catch(FileNotFoundException e){
System.out.println(“捕获异常:”+e); //e会调用toString()方法回显异常信息给程序员
}
catch(IOException e){
System.out.println("捕获异常:"+e);
}
finally{
System.out.println("finally块总是执行!");
} }
}

运行结果:
捕获异常:java.io.FileNotFoundException: text.txt (系统找不到指定的文件。)
finally块总是执行!

&emsp;
&emsp;

3.2 try语句

try{}:将可能抛出一个或者若干个异常的代码放入try语句块中。
注意:应当尽量减小try代码块的大小,不要将整个程序代码全部放入try语句块中,而是应当仔细分析代码,在可能出现异常情况的地方用try进行监控。

因为当发生异常时,程序控制由try块转到catch块,Java将跳过try中后面的语句,且永远不会从catch块返回到try块。因此若将整个程序代码都放在try中,若一开始发生异常,则后面的语句将永远不会被执行,从而影响了程序的实现。

&emsp;

3.3 catch语句

**📒(1)** **try语句后面必须跟有一个或多个catch语句来处理try中产生的异常事件**。如果try语句中未产生异常, 那么catch语句将不执行。 **📒(2)catch语句需要一个参数:****一个异常类名和该异常类的对象。注意该异常类必须是Throwable类的子类**. **📒(3)** try块中发生了一个异常,try-catch语句就会自动在try块后面的各个catch块中,找出与该异常类相 匹配的参数。当参数符合以下3个条件之一时,就认为这个参数与产生的异常相匹配: (1)参数与产生的异常属于一个类; (2)参数是产生的异常的父类; (3)参数是一个接口时,产生的异常实现了这一接口。 **📒(4) 注意:** (1) 当产生的异常找到了第一个与之相匹配的参数时,就执行包含这一参数的catch语句中的Java代码,执 行完catch语句后,程序恢复执行,但不会回到异常发生处继续执行,而是执行try-catch结构后面的代码。 (2) 可以用一个catch块来处理多个异常类型,此时catch的参数应该是这多个异常的父类。 (3) 有多个catch块时,要细心安排catch块的顺序。 **将子类的catch块放在前面,父类的catch块放在后面。**

&emsp;

3.4 finally子句

**1.finally语句:** 无论在try块中是否产生异常,也不管产生的异常是否会被捕获,finally中的语句最终都会 被执行。 **2.作用:** 为异常处理事件提供一个清理机制,例如清理打开文件、Socket、JDBC连接之类的资源。

如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。
&ensp;finally语句可以说是为异常处理事件提供的一个清理机制. 一般是用来关闭文件或释放其他的系统资源,作
为try-catch-finally结构的一部分,可以没有finally语句,如果存在finally语句,不论try块中是否发生
了异常,是否执行过catch语句,都要执行finally语句。

3. 带有finally子句的try-catch-finally语句的形式如下:

1
2
3
4
5
6
try { … }           //…是正常执行的代码, 可能产生异常
catch (异常类1 e) { … } //…是异常类1的处理代码
catch (异常类2 e) { … } //…是异常类1的处理代码
……
catch (异常类n e) { … } //…是异常类2的处理代码
finally { … } //…是执行清除工作的语句
**4.执行过程:** **(1) try块中的语句没有产生异常。**在这种情况下,Java首先执行try块中的所有的语句,然后执行finally子句 中的代码,最后执行try…catch..finally块后面的语句; **(2) try块中的语句产生了异常,而且此异常在方法内被捕获(有catch匹配)。** 在这种情况下,Java首先执行try 块中的语句,直到产生异常处,然后跳过此try块中剩下的语句,执行捕获此异常的catch子句的处理代码; 然后执行finally子句中的代码; **(3) 如果在catch子句又重新抛出了异常。**也会执行finally,然后将这个异常抛出给方法的调用者; **(4) try块中产生了异常,而此异常在方法内没有被捕获(没有catch匹配) 。**在这种情况下,Java将执行try块 中的代码直到产生异常,然后跳过try块中的代码而转去执行finally子句中的代码,最后将异常抛出给方法 的调用者。

&emsp;
&emsp;

四、 throws-throw抛出异常

4.1 throws抛出异常

📒throws说明:
在设计可能会抛出异常的方法时,可以有两个选择:
(1) 使用try-catch-finally处理方法中的异常
(2) 声明抛出异常:不捕获异常(没有throws方法的创建异常类的实例和抛出异常。是个空语句。),而是将异常交由上一层处理,在其他地方捕获异常。如果使用后者,那么应该(在某些情况下)向编译器表明:此方法可能会抛出异常,但方法本身不会捕获它。可以在方法头中用throws子句来实现此功能。

(2.1)带throws异常说明的方法说明形式如下:
… 方法名(…) [throws 异常类列表]
{ 方法体 }

&emsp;
📒注意:
(1)方法抛出的异常类是throws子句中指定的异常类或其子类。
(2)并不是所有可能发生的异常都要在方法的说明中指定,从Error类中派生出的异常和从RuntimeException类中派生的异常就不用在方法声明中指定。

📒在下列情况下Java方法可以声明抛出异常:
(1)调用的方法抛出了异常;
(2)检测到了错误并使用throw语句抛出异常;

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
1:调用的方法抛出了异常 
class Test {……
public String getInput() throws IOException
{……
System.in.read();
}
}



2:检测到了错误并使用throw语句抛出异常
import java.io.*;
class Test {……
public String getInput() throws IOException
{……
IOException ae =new IOException("buffer is full");
throw ae;
}



3throws多个异常
class Animation
{
public Image loadImage(String s) throws
EOFException, MalformURLException
{
……
}
}

&emsp;
&emsp;

4.2 throw抛出异常

📒throw说明:
要使用throw,则必须在方法名后面指出throws;但是使用throws,抛出可以是空语句不用写,交给上一层处理!!
在捕获一个异常前,必须有一段Java代码来生成和抛出一个异常对象。Java用throw语句抛出异常。throw语句的格式如下:
throw ThrowableObject;

&emsp;
异常对象的生成和抛出可以有以下三种情况:
   (1)Java运行时系统
   (2)JDK中某个类
   (3)在程序中创建异常对象抛出

使用throw语句应注意:
(1)一般这种抛出异常的语句应该在满足一定条件执行,例如把throw语句if分支中
(2)含有throw语句的方法,应该在方法头定义中用throws语句声明所有可能抛出的异常

抛出异常有这样三步:
(1)确定异常类;
(2)创建异常类的实例;
(3)抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
举例
static String getInput() throws IOException{
char[] buffer =new char[20];
int counter = 0;
boolean flag =true;
while(flag) {
buffer[counter] =(char)System.in.read();

if(buffer[counter]=='\n') flag = false;
counter++;
if(counter>=20){
} IOException ae =new IOException("buffer is full");
throw ae; }
return new String(buffer);
}

"//IOException ae =new IOException(""buffer is full""); throw ae;"使用throw

&emsp;
&emsp;

五、 正确地使用异常

由于异常使用起来非常方便,以至于在很多情况下可能会滥用异常。但是,使用异常处理会降低程序运行的 速度,几点建议:

(1) 在可以使用简单的测试就能完成的检查中,不要使用异常来代替它。例如:
if (ins!=null) //使用ins引用对象
{ … }

(2) 不要过细地使用异常。最好不要到处使用异常,更不要在循环体内使用异常处理, 可以将它包裹在循环
体外面。
(3)不要捕获了一个异常而又不对它做任何的处理。
try
{
…… //正常执行的代码
}
catch(Exception e) { }

(4) 将异常保留给方法的调用者并非不好的做法。
对于有些异常,将其交给方法的调用者去处理是一种更好的处理办法。没有类似这样if(counter>=20){
IOException ae =new IOException(“buffer is full”);
throw ae; }的语句,是个空语句。



&emsp;
&emsp;
&emsp;
&emsp;