认识异常
异常是指程序的运行过程中所发生的不正常事件,如所需文件找不到,网络连接不通或连接中断、算术运算出错(如被零整除)、数组下标越界、装载一个不存在的类、对null对象操作、类型转换异常。异常会中断正在运行的程序。
所有异常类型都是Throwable类的子类,它派生了两个子类:Error类和Exception类。
(1).Error类:表示仅靠程序无法恢复的严重错误,如内存溢出、动态链接失败、虚拟机错误,应用程序不应该抛出这种类型的错误(一般由虚拟机抛出)。假如出现这种错误,应尽力使程序安全退出。
(2)Exception类:由Java应用程序抛出和处理的非严重错误,如所需文件找不到,网络连接不同通或连接中断,算术运算出错(如被零除)、数组下标越界,装载一个不存在的类,对null对象操作,类型转换异常等。它的各种不同的子类分别对应不同类型的异常,Exception又可分为两大类异常。
a.运行异常:包括RuntimeException及其所有子类。不要求程序对他们进行处理,如算术异常ArithmeticException
b.Checked异常(非运行时异常):除了运行时异常外的其他从Exception类继承来的的异常
下表列出常见的异常:
异常 | 说明 |
---|---|
Exception | 异常层次结构的根类 |
ArithmeticException | 算术错误异常,如以零作为除数 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
NullPointerException | 尝试访问null对象成员 |
ClassNotFoundException | 不能加载所需要的类 |
InputMismatchException | 欲得到的数据类型与实际输入的类型不匹配 |
IllegalArgumentException | 方法接收到非法参数 |
ClassCastException | 对象强制类型转换出错 |
NumberFormatException | 数字格式转换异常,如把“abc”转换成数字 |
异常处理
异常处理机制就像人们对平时可能遇到的意外情况,预先想好了一些处理的办法。在程序执行代码时,若发生了异常,程序会按照预定的处理办法对异常进行处理,异常处理完毕后,程序继续运行(不加异常处理,程序不会继续运行下面的代码)。
Java的异常处理是通过5个关键字来实现的,即try、catch、finally、throw和throws。
Try-catch处理异常
Java中提供了try-catch结构进行异常捕获处理,把可能出现异常的代码放入try语句块中,并使用catch语句捕获异常。
快捷键:选中代码,同时按下Ctrl+Alt+t
就可以捕获异常。
使用try-catch捕获并处理示例1中的异常
public static void main(String[] args) {
try {
Scanner scanner =new Scanner(System.in);
System.out.println("请输入被除数");
int num1 =scanner.nextInt();
System.out.println("请输入除数:");
int num2=scanner.nextInt();
System.out.println(String.format("%d/%d=%d",num1,num2,num1/num2));
System.out.println("感谢使用本程序");
} catch (Exception e) {
System.err.println("出现错误:被除数和除数必须是整数,"+"除数不能为零");
e.printStackTrace();
}
}
分析:
try-catch语句块的执行流程比较简单,首先执行的是try语句块中的语句,这时可能会出现以下3种情况。
(1)如果try语句块中所有语句正常执行完毕,没有发生异常,那么catch语句块中的所有语句都将会被忽略。当在控制台输入两个整数时,示例3中的try语句块中的代码将正常执行,不会执行catch语句块中的代码。输出结果
请输入被除数
200
请输入除数:
4
200/4=50
感谢使用本程序
(2)如果try语句块在执行过程中发生异常,并且这个异常与catch语句块中声明所示。的异常类型匹配,那么try语句块中剩下的代码都将被忽略,而相应的catch语句块将会被执行。匹配是指catch所处理的异常类型与所生成的异常类型完全一致或是它的父类。当在控制台提示输出被除数时,输入了"B",示例3中try语句块中的代码:
"int num1 =in.nextInt();"将抛出InputMismatchException异常。由于InputMismathException是Exception的子类,程序将忽略try语句中剩下的代码而去执行catch语句块。输出结果如下
请输入被除数
200
请输入除数:
B
出现错误:被除数和除数必须是整数,除数不能为零
java.util.InputMismatchException
如果输入的除数为0,输出的结果如下
请输入被除数
200
请输入除数:
0
出现错误:被除数和除数必须是整数,除数不能为零
java.lang.ArithmeticException: / by zero
(3)如果try语句块在执行过程中发生异常,而抛出的异常在catch语句中没又被声明,那么方法立刻退出
如示例3所示,在catch语句块中可以加入用户自定义处理信息,也可以调用异常对象的方法输出异常信息,常用方法如下:
a.void printStackTrace():堆栈信息包括程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用实例。
b.String getMessage():返回异常信息描述的字符串,该字符串描述了异常产生的原因,是printStackTrace()输出的信息的一部分。
注意:
如果try语句块在执行过程中发生异常,try语句块中剩下的代码都将被忽略,系统会自动生成相应的异常对象,包括异常的类型、异常出现时程序的运行状态及对该异常的详细描述。如果这个异常对象与catch语句块中声明的异常类型匹配,会把该异常对象赋给catch后面的异常参数,相应的catch语句块将会被执行。
Try-catch-finally处理异常
如果希望示例3中不管是否发生异常,都执行输出”感谢使用本程序!“语句,就需要在try-catch语句块后加入finally语句块,把要执行输出的语句放入finally语句块中,无论是否发生异常,finally语句块中的代码总能被执行。
public static void main(String[] args) {
try {
Scanner scanner =new Scanner(System.in);
System.out.println("请输入被除数");
int num1 =scanner.nextInt();
System.out.println("请输入除数:");
int num2=scanner.nextInt();
System.out.println(String.format("%d/%d=%d",num1,num2,num1/num2));
} catch (Exception e) {
System.err.println("出现错误:被除数和除数必须是整数,"+"除数不能为零");
e.printStackTrace();
}finally {
System.out.println("/by zero");
System.out.println("感谢使用本程序");
}
}
分析:
try-catch-finally语句块的执行流程大致分为如下两种情况。
(1)如果所语正常执行完毕,finally语句块也会被执行,例如,当在控制台输入两个数字时,示例4中的try语句块中的代码将正常执行,不会执行 catch-fnally代将被执行输出结果如下所示。
请输入被除数
200
请输入除数:
0
/by zero
感谢使用本程序
出现错误:被除数和除数必须是整数,除数不能为零
(2)如果异常,无这种异常能否被catch语句块捕获到,将行finally语句中的代码。例,当在控制台输入的除数为0时,示例4中的try语句块中将抛出异常,进入catch句块finally语句块中的代码也将被执行输出结果如图97所示。
try-catch-finally结构中try语句块是必须存在的,catch、finally语句块为可选,但两者至少出现其中之一。
需要特别注意的是,即使在catch语句块中存在retun语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执行catch块中retun之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语句退出。
finally语句块中语句不执行的唯一情况是在异常处理代码中执行System.exit(1)退出Java虚拟机。如下示例5
示例
在try-catch-finally结构的catch语句块中执行System.exit(1)退出Java虚拟机
public static void main(String[] args) {
try {
Scanner scanner =new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 =scanner.nextInt();
System.out.println("请输入除数");
int num2=scanner.nextInt();
System.out.println(String.format("%d/%d=%d",num1,num2,num1/num2));
} catch (Exception e) {
System.err.println("出现错误:被除数和除数必须是整数,"+"除数不能为零");
System.exit(1); //finally 语句块不执行的唯一情况
//return;如果用return,finally代码块依旧执行
} finally {
System.out.println("感谢使用本程序");
}
}
使用多重catch处理异常
在计算并,输出商的示例中,至少存在两种异常情况,输入非整数内容和除数为0,在示例4中,统一按照Exception类型捕获,其实可以使用多重catch语句块分别捕获并处理对应异常。
一段代码可能会引发多种类型的异常,这时,可以在一个try语句块后面跟多个catch语句块分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般都是Exception类。因为按照匹配原则,如果父类异常放到前面,后面的catch语句块将不会有被执行机会。
运行时,系统从上到下分别对每个catch语句块处理的异常类型进行检测,并执行第一个与异常类型匹配的catch语句。执行其中的一条catch语句后,其后的catch语句将被忽略。
示例
对示例4进行修改
public static void main(String[] args) {
try {
Scanner scanner =new Scanner(System.in);
System.out.println("请输入被除数");
int num1 =scanner.nextInt();
System.out.println("请输入除数");
int num2 =scanner.nextInt();
System.out.println(String.format("%d/%d=%d",num1,num2,num1/num2));
} catch (InputMismatchException e) {
System.err.println("被除数和除数必须是整数。");
} catch (ArithmeticException e){
System.err.println("除数不能为0");
}catch (Exception e){
System.err.println("其他未知的异常");
}finally {
System.out.println("感谢使用本程序");
}
}
程序运行后,如果输入的不是整数,系统会抛出InputMismatchException异常对象,因此进入第一个catch语句块,并执行其中的代码,而其后的catch语句块将被忽略。输出结果如下:
请输入被除数
0.2
感谢使用本程序
被除数和除数必须是整数。
如果系统提示输入被除数时输入“200”,系统会接着提示输入除数,当输入“0”时,系统会抛出ArithmeticException异常对象,因此进入第二个catch语句块并执行其中的代码块,其他的catch语句块将被忽略,如下:
请输入被除数
200
请输入除数
0
感谢使用本程序
除数不能为0
提示:
在使用多重catch语句块时,catch语句块的排列顺序必须是从子类到父类,最后一个一般都是Exception类。下面代码片断是错误的。
提示爆红:Exception ‘java.lang.ArithmeticException’ has already been caught
已捕获异常“java.lang.ArithmeticException”
原因:我们父类异常放在子类的异常前面,然而父类的异常涵盖了子类的异常
示例:
catch (Exception e){
System.err.println("其他未知的异常");
} catch (ArithmeticException e){ //提示爆红
System.err.println("除数不能为0");
}finally {
System.out.println("感谢使用本程序");
}
使用throws抛出异常
如果一个方法体中抛出了异常,并希望调用者能够及时的捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的方法中异常以通知调用者。throws可以同时声明多个异常,之间用,号隔开
下面的示例,把计算输出商的任务封装在了divide()方法中,并在方法的参数列表后通过throws声明抛了异常,然后在main()方法就知道divide()方法中抛出了异常,可以采用如下两种方法进行处理
1).通过try-catch捕获并处理·异常
2)通过throws继续声明异常,如果调用者不知道如何处理该异常,可以继续通过throws声明异常,让上一级调用者处理异常。main()方法的异常将由Java虚拟机处理。
示例:
在Java程序中使用throws声明抛出异常
public static void main(String[] args) {
try {
divide();
} catch (InputMismatchException e) {
System.err.println("被除数和除数必须是整数");
} catch (ArithmeticException e) {
System.err.println("除数不能为0");
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("感谢使用本程序!");
}
}
//通过throws声明抛出异常
public static void divide() throws Exception{
Scanner scanner =new Scanner(System.in);
System.out.println("请输入被除数");
int num1 =scanner.nextInt();
System.out.println("请输入除数");
int num2 =scanner.nextInt();
System.out.println(String.format("%d/%d=%d",num1,num2,num1/num2));
}
使用thow抛出异常
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现解决的,如果年龄不在正常范围内,性别输入不是“男”或“女”等,此时需要程序而不是系统来自行抛出异常,把问题交给调用者去解决。
在Java语言中,可以使用throw关键字来自行抛出异常。在下面示例9的带码中抛出了异常,抛出异常的原因在于当前环境无法解决参数问题,因此在方法内部通过throw抛出异常,把问题交给调用者解决。
示例:
在Java程序中使用throw抛出异常。
1).先创建一Person类
public class Person {
private String name=""; //姓名
private int age =0; //年龄
private String sex="男"; //性别
//设置性别
public void setSex(String sex)throws Exception{
if ("男".equals(sex)||"女".equals(sex)) {
this.sex=sex;
}else {
throw new Exception("性别必须是\"男\"或者\"女\"!");
}
}
//输出基本信息
public void print(){
System.out.println(this.name+"("+this.sex+","+this.age+"岁)");
}
}
2).测试
public static void main(String[] args) {
Person person = new Person();
try {
person.setSex("man");
person.print();
} catch (Exception e) {
e.printStackTrace();
}
}
3)运行结果:
java.lang.Exception: 性别必须是"男"或者"女"!
at com.shujia.Person.setSex(Person.java:23)
at com.shujia.Test8.main(Test8.java:17)
Process finished with exit code 0
注意:
throw和throws的区别如下:
1).作用不同:throw用于程序员自行产生并抛出异常,throws用于声明该方法内抛出了异常。
2).使用的位置不同:throw位于方法体内部,可以作为单独语句使用;throws必须跟在方法参数列表的后面,不能单独使用。
3)内容不同:throw抛出一个异常对象,只能是一个;throws后面跟异常类,可以跟多个。
自定义异常
当JDK中的异常类型不满足我门需求,我门就可以自定义异常。
自定义异常:
1.定义异常类,继承Exception或者RuntimeException
2.编写一个异常类并编写一个构造方法,并继承父类的实现。
public Exception() {
super();//调用了父类的无参构造方法
}
public Exception(String message) {
super(message);//调用了父类里面带String的参数的构造方法
}
//现在所有的Throwable的子类构造器中都可以接收一个cause对象作为参数,这这参数就是异常的原由,代表的原始异常,即使在当前位置创建并抛出行的异常,也可以通过这个cause追踪到异常最初发生的位置。
public Exception(String message, Throwable cause) {
super(message, cause);//调用了父类含有cause参数的构造函数
}
public Exception(Throwable cause) {
super(cause);
}
异常链
背景: 在异常处理时:A方法调用了B方法,B方法抛出了异常,那么A方法是继续抛出原有的异常还是抛出个新异常
如果抛出原有的异常将是很糟糕的设计方法。因为A方法与B方法进行了关联,不便于代码的修改和扩展,若抛出新的异常,虽然解决了A方法和B方法的关联问题,但是原有的异常信息却会丢失。JDK1.4推出了异常链,解决了这个问题,所有的Throwable的子类在构造器中都可以接收一个cause对象作为参数。这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,你也能通过这个异常链跟踪到异常最初发生的位置。
通俗的来讲:把捕获的异常包装成一个新的异常,在新的异常中添加对新的异常的引用,再把新异常抛出,就像是链式反应一样,这种就叫异常链。
总结:
1.异常就是在程序的运行过程中所发生的异常事件
2.Java处理异常通过5个关键字来实现,,try、catch、finally、throw、throws
3.即使在try语句块,catch语句中存在return语句,finally语句块也会去执行,finally语句块中不执行的原因,唯一原因,在异常处理代码中执行了System.exit(1).跳出了Java虚拟机
4.可以在try语句块后面跟多个catch语句块,分别处理不同的异常,但是排序必须是从·特殊异常到一般异常,最后一个一般是Exception类
5.Java语言通过关键字throws声明某个方法可能抛出的各种异常以通知调用者
6.在Java语言中,通过throw关键字来自行抛出异常
7.自定义异常一般需要继承Execption或者RuntimeException