导学
本节课程,我们将会学习到异常,以及如何处理可能会发生的异常。
首先,什么是异常?异常可以理解为意外,例外的意思,本质上是程序出现的错误。错误在我们编写程序的过程中经常会发生,包括编译期间和运行期间的错误。比如括号没有正常的配对,语句少写了分号,关键字编写错误等就是编译期间会出现的错误。通常这些编译错误编译器会帮助我们进行修订。
那么,运行期间的错误,我们也曾遇到过。比如使用空的对象引用调用方法、数组访问时下标越界、算数运算除数为0、类型转换时无法正常转型等,这些错误在编译的时候完全没有提示。
在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以理解为异常。
那么,为什么会出现异常呢?
用户不正当的输入 本身系统代码编写的问题
如果不对异常进行处理,那么轻则数据错误、丢失,重则程序崩溃。比如下面这段代码
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中,提供了一种强大的异常处理机制来帮助我们实现异常的处理
异常介绍
Error是程序无法处理的错误,表示运行应用程序中较为严重的问题,是代码运行时Java虚拟机中出现的问题。这种错误是不可查的,它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。 对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。 Excepetion是程序本身可以处理的异常。异常处理通常针对这种类型异常的作出处理 。
RuntimeException
是非检查异常,也就是编译器不要求强制处理的异常。程序员可以针对这些异常进行捕获或放任不管。检查异常包括IO异常和SQL异常,编译器要求必须在代码中处理这些异常。
异常处理
针对于异常处理,通常我们最简单的方式就是思考全面,检查代码。将可能会发生的错误预先考虑透彻。那么也有可能会出现一些无法预料到的情况,我们就可以使用到Java的异常处理机制。在Java中异常处理机制分为两种:抛出异常和捕获异常。
抛出异常指的是一个方法中出现错误引发的异常时,方法会创建异常对象,交给运行时系统进行处理。在异常对象中包含异常类型,异常出现时的程序状态等。运行时系统捕获到这个异常后,进入捕获异常环节,运行时系统会找合适的处理器,与抛出异常匹配后进行处理,如果没找到则程序终止。Java规定,对于检查异常必须捕获、或者声明抛出。对于非检查异常(RuntimeException及其子类)和Error及其子类,允许忽略。
抛出异常、捕获异常通过5个关键字实现:try、catch、finally、throw、throws。try、catch、finally通常用来捕获异常,throws通常用来声明异常,throw通常用来抛出异常。
try…catch…finally
语法:
try {
//用于捕获异常
可能会发生异常的代码块;
} catch(Exception e) {//异常类型
//用于处理try中捕获的异常
} finally {
//无论是否发生异常都会执行的代码
}
try块后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 也就是说try必须和catch,finally组合使用。catch、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(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;
}
}
package com.dodoke;
import java.util.InputMismatchException;
import java.util.Scanner;
public class ExceptionClass {
public static void main(String[] args) {
try {
int result = test();
System.out.println("运行的结果为:" + result);
} catch(ArithmeticException e) {
System.out.println(e.getMessage());
}
//test();写exception异常后会有提示
}
public static int test() {//告诉编译器该方法可能会发生何种异常,可以使用多异常或写Exception
Scanner input = new Scanner(System.in);
System.out.println("======运算开始=======");
System.out.println("请输入第一个整数");
int one = input.nextInt();
System.out.println("请输入第二个整数");
int two = input.nextInt();
if (two == 0){
throw new ArithmeticException("除数不能为0");
}
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抛出异常对象的处理方案:
- 自己抛出自己处理,通过try-catch包含throw语句。
- 用throws往上抛,调用者可以try-catch处理或者继续往上抛。throws抛出异常类型时,要抛出与throw对象相同的类型或者其父类,但不能是子类
PS:throw手动抛出的异常不建议使用非检查类型,因为编译器不提示
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。也可以通过自定义异常描述特定业务产生的异常类型。所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类。
比如,在之前酒店的例子中,自己新设置的异常信息被多次使用,就可以创建一个异常类
public class HotelAgeException extends Exception{
public HotelAgeException (){
super("18岁以下住店必须由亲友陪同");
}
}
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;
}
}
}
异常链就是一个异常接着一个异常。最后输出的异常就只有最后一个。
如果想要把前面所有异常都输出的话,则需要通过保留异常的方法:
- 通过构造方法对旧异常对象的获取
Throwable(String message, Throwable cause) — 保留底层异常的异常信息。 - 通过initCause(Throwable cause)方法(一个异常的信息来初始化一个新的异常)用来获取原始异常的描述信息,其中cause是原始异常的对象