# Java异常
[TOC]
## 导学
本节课程,我们将会学习到异常,以及如何处理可能会发生的异常。
首先,什么是异常?异常可以理解为意外,例外的意思,本质上是程序出现的错误。错误在我们编写程序的过程中经常会发生,包括编译期间和运行期间的错误。比如括号没有正常的配对,语句少写了分号,关键字编写错误等就是编译期间会出现的错误。通常这些**编译错误编译器会帮助我们进行修订。**
那么,运行期间的错误,我们也曾遇到过。比如使用空的对象引用调用方法、数组访问时下标越界、算数运算除数为0、类型转换时无法正常转型等,这些错误在编译的时候完全没有提示。
>[info]在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以理解为异常。
那么,为什么会出现异常呢?
>[danger]用户不正当的输入
本身系统代码编写的问题
如果不对异常进行处理,那么轻则数据错误、丢失,重则程序崩溃。比如下面这段代码
~~~Java
public class ExceptionStudy {
public static void main(String[] args) {
System.out.println("******程序开始运行*******");
System.out.println("******数学计算:"+ (10/2) +"*******");
System.out.println("******程序运行结束*******");
}
}
运行结果:
******程序开始运行*******
******数学计算:5*******
******程序运行结束*******
public class ExceptionStudy {
public static void main(String[] args) {
System.out.println("******程序开始运行*******");
System.out.println("******数学计算:"+ (10/0) +"*******");
System.out.println("******程序运行结束*******");
}
}
运行结果:
******程序开始运行*******
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ntdodoke.preparation.usuallyClass.ExceptionStudy.main(ExceptionStudy.java:6)
~~~
在出现异常的地方,程序就会被「异常指令流」直接终止,不再往下运行。为了让程序在出现异常后依然可以正常执行,所以我们必须正确处理异常来完善代码的编写。
在Java中,提供了一种强大的异常处理机制来帮助我们实现异常的处理
## 异常介绍
![](https://img.kancloud.cn/bb/fe/bbfe014fb1278b4c2aae48fa8003447f_1008x568.png)
>[info]Error是程序无法处理的错误,表示运行应用程序中较为严重的问题,是代码运行时Java虚拟机中出现的问题。这种错误是不可查的,它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
>[danger]Excepetion是程序本身可以处理的异常。异常处理通常针对这种类型异常的作出处理 。`RuntimeException`是非检查异常,也就是编译器不要求强制处理的异常。程序员可以针对这些异常进行捕获或放任不管。检查异常包括IO异常和SQL异常,编译器要求必须在代码中处理这些异常。
## 异常处理
针对于异常处理,通常我们最简单的方式就是思考全面,检查代码。将可能会发生的错误预先考虑透彻。那么也有可能会出现一些无法预料到的情况,我们就可以使用到Java的异常处理机制。在Java中异常处理机制分为两种:抛出异常和捕获异常。
抛出异常指的是一个方法中出现错误引发的异常时,方法会创建异常对象,交给运行时系统进行处理。在异常对象中包含异常类型,异常出现时的程序状态等。运行时系统捕获到这个异常后,进入捕获异常环节,运行时系统会找合适的处理器,与抛出异常匹配后进行处理,如果没找到则程序终止。Java规定,对于检查异常必须捕获、或者声明抛出。对于非检查异常(RuntimeException及其子类)和Error及其子类,允许忽略。
抛出异常、捕获异常通过5个关键字实现:try、catch、finally、throw、throws。try、catch、finally通常用来捕获异常,throws通常用来声明异常,throw通常用来抛出异常。
![](https://img.kancloud.cn/97/e5/97e5419c8a0e3b1bffcff948c552de85_557x272.png)
### try...catch...finally
语法:
~~~
try {
//用于捕获异常
可能会发生异常的代码块;
} catch(Exception e) {//异常类型
//用于处理try中捕获的异常
} finally {
//无论是否发生异常都会执行的代码
}
~~~
**try块后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。** 也就是说try必须和catch,finally组合使用。catch、finally也不允许单独存在。
案例:
~~~java
public class TryDemo {
public static void main(String[] args) {
//定义两个整数,输出两个数的商
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
try{
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
}catch(Exception e) {//这是对异常种类的猜测,通常可以采用异常的父类,就不用猜测具体是哪个子类异常了
e.printStackTrace();//在控制台打印出异常种类,错误信息和出错位置等,输出位置比较随机
System.out.println("程序出错了~~~");
} finally {
System.out.println("运算结束");
}
}
}
~~~
在Java中,可以使用多重catch语句块,针对于同一块代码可能发生的不同种类异常作出处理。比如上述代码可能会发生算术异常也可能会发生其他异常。但是两个catch块中对于异常的猜测是不允许相同的,而且对于父类的异常是必须要放置在子类的异常catch代码块下面的。兄弟异常的catch代码块位置可以随意。
~~~
public class TryDemo {
public static void main(String[] args) {
//定义两个整数,输出两个数的商
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
try{
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
} catch(InputMismatchException a) {//输入格式异常
System.out.println("请输入整数!");
a.printStackTrace();
} catch(ArithmeticException b) {//数学运算异常
System.out.println("除数不能为0!");
b.printStackTrace();
} catch(Exception e) {//这是对异常种类的猜测,通常可以采用异常的父类,就不用猜测具体是哪个子类异常了
e.printStackTrace();//在控制台打印出异常种类,错误信息和出错位置等,输出位置比较随机
System.out.println("程序出错了~~~");
} finally {
System.out.println("运算结束");
}
}
}
~~~
我们会发现,最终输出的异常信息,是我们猜测的某一个异常。同样我们不可能猜测出所有的异常信息,所以需要在catch代码块的最下面写出一个父类异常catch代码块,以免发生遗漏!
#### 终止finally执行的方法
在之前的课程中,我们可以看到,finally语句块中的内容无论怎样都会运行,那么有没有一种方法可以终止finally中方法的运行呢
~~~
public class TryDemo {
public static void main(String[] args) {
//定义两个整数,输出两个数的商
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
try{
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
} catch(InputMismatchException a) {//输入格式异常
System.exit(1);//终止程序运行
System.out.println("请输入整数!");
a.printStackTrace();
} catch(ArithmeticException b) {//数学运算异常
System.out.println("除数不能为0!");
b.printStackTrace();
} catch(Exception e) {//这是对异常种类的猜测,通常可以采用异常的父类,就不用猜测具体是哪个子类异常了
e.printStackTrace();//在控制台打印出异常种类,错误信息和出错位置等,输出位置比较随机
System.out.println("程序出错了~~~");
} finally {
System.out.println("运算结束");
}
}
}
~~~
`System`中存在`exit`方法,该方法的作用可以用来终止Java虚拟机的运行,传入的参数是数字,这个数字只要不是数字0描述的都是异常终止状态。可以使用该方法来使当前程序无条件终止运行。
#### return关键字在异常处理中的作用
在之前的学习中,我们知道return可以用来提供方法的返回值。那么return是否可以用在异常处理中呢,或者又有什么作用呢
~~~
public class TryTest {
public static void main(String[] args) {
int result = test();
System.out.println("运行的结果为:" + result);
}
public static int test() {
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
try{
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
return one / two;
} catch(ArithmeticException b) {
System.out.println("除数不能为0!");
b.printStackTrace();
return 0;
} finally {
System.out.println("运算结束");
return -10000;
}
}
}
~~~
当try,catch,finally三个语句块中都存在return关键字时,无论程序运行正常不正常,最终的结果都是-10000;原因在于finally无论如何都会执行。
### throw和throws
#### 使用throws声明异常类型
可以通过throws声明将要抛出何种类型的异常,通过throw将产生的异常抛出。如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。谁调用这个方法则谁处理抛出的异常。
throws 后面可以跟多个异常类型,多个异常使用逗号隔开。当方法抛出异常时,方法将不对这些类型及其子类类型异常做处理,而抛向调用该方法的方法,由他去处理。
~~~
public class TryTest {
public static void main(String[] args) {
try {
int result = test();
System.out.println("运行的结果为:" + result);
} catch(ArithmeticException e) {
System.out.println("要求除数不能为0");
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
//test();写exception异常后会有提示
}
public static int test() throws ArithmeticException, Exception{//告诉编译器该方法可能会发生何种异常,可以使用多异常或写Exception
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
System.out.println("one和two的商是:" + one / two);
System.out.println("运算结束");
return one / two;
}
}
~~~
如果方法抛出的是非检查异常,那么调用此方法处编译器不会强制要求进行异常处理(不会报错),如果方法抛出的异常是检查异常或者Exception,则调用此方法处编译器会强制要求进行异常处理(不处理会报错)。针对不报错的情况,可以在抛出异常的方法处加文档注释,这样调用此方法处虽然编译器不会提示要异常处理,但是文档注释会提示。
#### throw抛出异常对象
注意,throw抛出的是异常对象。throw抛出的只能是Throwable或者是其子类的实例对象。
~~~
用throws往上抛
public class TryTest {
//酒店入住规则:18以下和80岁以上必须有亲友陪同,不得单独入住
public static void testAge() throws Exception {
System.out.println("请输入年龄:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if(age < 18 || age > 80) {
throw new Exception("18岁以下,80岁以上的住客必须由亲友陪同入住");
} else {
System.out.println("欢迎入住本酒店!");
}
}
}
自己抛出自己处理
public class TryTest {
//酒店入住规则:18以下和80岁以上必须有亲友陪同,不得单独入住
public static void testAge() {
System.out.println("请输入年龄:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if(age < 18 || age > 80) {
try {
throw new Exception("18岁以下,80岁以上的住客必须由亲友陪同入住");
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("欢迎入住本酒店!");
}
}
}
~~~
throw抛出异常对象的处理方案:
1. 自己抛出自己处理,通过try-catch包含throw语句。
2. 用throws往上抛,调用者可以try-catch处理或者继续往上抛。throws抛出异常类型时,要抛出与throw对象相同的类型或者其父类,但不能是子类
PS:**throw手动抛出的异常不建议使用非检查类型,因为编译器不提示**
## 自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。也可以通过自定义异常描述特定业务产生的异常类型。所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。
比如,在之前酒店的例子中,自己新设置的异常信息被多次使用,就可以创建一个异常类
~~~
public class HotelAgeException extends Exception{
public HotelAgeException (){
super("18岁以下住店必须由亲友陪同");
}
}
~~~
>[info]getMessage()方法可以显示异常信息
## 异常链
~~~
public class TryClient {
public static void main(String[] args) {
try {
testThree();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testOne() throws HotelAgeException {
throw new HotelAgeException();
}
public static void testTwo() throws Exception {
try {
testOne();
} catch(HotelAgeException e) {
throw new Exception("我是新产生的异常1",e);//java中保留异常的机制
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch(Exception e) {
//throw new Exception("我是新产生的异常2",e);或写成另一种方式
Exception e1 = new Exception("我是新产生的异常2");
e1.initCause(e);
throw e1;
}
}
}
~~~
异常链就是一个异常接着一个异常。最后输出的异常就只有最后一个。
如果想要把前面所有异常都输出的话,则需要通过保留异常的方法:
1. 通过构造方法对旧异常对象的获取
Throwable(String message, Throwable cause) -- 保留底层异常的异常信息。
2. 通过initCause(Throwable cause)方法(一个异常的信息来初始化一个新的异常)用来获取原始异常的描述信息,其中cause是原始异常的对象
## 练习
一、选择
1. 下列代码中的异常属于(多选)
![](https://img.kancloud.cn/36/31/36315a89b1cf56b4230a47a6ec7a0704_392x77.png)
~~~
A. 非检查型异常
B. 检查型异常
C. Error
D. Exception
~~~
2. 类及其子类所表示的异常是用户程序无法处理的
~~~
A. NumberFormatException
B. Exception
C. Error
D. RuntimeException
~~~
3. 数组下标越界,则发生异常,提示为
~~~
A. IOException
B. ArithmeticException
C. SQLException
D. ArrayIndexOutOfBoundsException
~~~
4. 运行下列代码,当输入的num值为a时,系统会输出
![](https://img.kancloud.cn/3a/1f/3a1fd9e51c508fa4a77381e997536290_451x259.png)
~~~
A. one three end
B. two three end
C. one two three end
D. two end
~~~
5. 运行下列代码,输出结果为
![](https://img.kancloud.cn/10/59/1059a7a071b0529a865fab54b8b90bf0_448x309.png)
~~~
A. a = 0
B. a = 0
除数不允许为0
C. a = 1
数组越界
D. a = 0
除数不允许为0
数组越界
~~~
6. 下列关于异常的描述,错误的是(多选)
~~~
A. printStackTrace()用来跟踪异常事件发生时执行堆栈的内容
B. catch块中可以出现同类型异常
C. 一个try块可以包含多个catch块
D. 捕获到异常后将输出所有catch语句块的内容
~~~
7. 假设要输入的id值为a101,name值为Tom,程序的执行结果为
![](https://img.kancloud.cn/10/7a/107a2fd226c19a334f8690c19529bb23_452x237.png)
~~~
A. id=a101
name=Tom
B. id=a101
name=Tom
输入结束
C.【输入数据不合规范。Try again】
D.【输入数据不合规范。Try again】
输入结束
java.util.InputMismatchException...
~~~
8. 下列代码的运行结果为
![](https://img.kancloud.cn/e5/f5/e5f5c6a6f07ccf8f1f278e0a5827c777_452x355.png)
~~~
A. 1
B. 10
C. 20
D. 30
~~~
9. 在下列代码划线处不可以填入选项中的哪一个异常类型
![](https://img.kancloud.cn/73/fa/73fa7a95fb33e39373a1f73b4e9168f6_859x277.png)
~~~
A. Throwable
B. Exception
C. InputMismatchException
D. ArithmeticException
~~~
10. 假设有自定义异常类MyException,那么抛出该异常的语句正确的是
~~~
A. throw new Exception()
B. throw new MyException()
C. throw MyException
D. throws Exception
~~~