一、Java中的异常处理机制
1.1Java中异常的概念
在Java程序运行过程中出现的错误统称为异常。
例子:算术异常,通常是被零除时发生的异常,是运行时异常public class ExceptionTest01 {
public static void main(String[] args){
int i = 30;
int j = 0;
//被零除,会抛出算术异常
int k = i/j;
}
}
控制台显示:Exception in thread "main"java.lang.ArithmeticException: / by zero
at com.weixin.test1.ExceptionTest01.main(ExceptionTest01.java:10)
例子:经典的空指针异常,当引用为null是发生的异常,是运行期异常public class ExceptionTest02 {
public static void main(String[] args){
Student stu = null;
//stu没有引用Student类型的对象,会抛出空指针异常
stu.print("张三", 20);
}
}
class Student{
public void print(String name,int age){
System.out.println("name: " + name + " age: " + age);
}
}
控制台显示:Exception in thread "main" java.lang.NullPointerException
at com.weixin.test1.ExceptionTest02.main(ExceptionTest02.java:9)
例子:数字格式化异常,通常是把非数字字符串转换成数值类型是发生的异常,是运行时异常public class ExceptionTest03 {
public static void main(String[] args){
String str = "abc";
//将字符串abc转换成整型值会抛出数字格式化异常
int i = Integer.parseInt(str);
}
}
控制台显示:Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at com.weixin.test1.ExceptionTest03.main(ExceptionTest03.java:9)
1.1.1异常的概念
通过上面的例子可以看出,Java中的异常就是在程序运行过程中出现的非正常情况,类似与我们日常生活中出现的不可预知的情况,例如:出租车上学的过程中出租车突然出现的故障让我们迟到等,就是正常情况下不会发生的事件,但是他发生了。当发生异常时,正常的情况会发生变化,在Java中程序会终止继续执行并退出虚拟机。
例子:当发生异常情况时,Java虚拟机抛出异常信息后,程序会终止运行public class ExceptionTest04 {
public static void main(String[] args){
int i = 20;
int j = 0;
int k = i/j;
//当发生异常时,程序终止执行
System.out.println("程序执行到这里了吗!");
}
}
控制台显示:Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.weixin.test1.ExceptionTest04.main(ExceptionTest04.java:10)
1.1.2异常的作用
当程序在运行过程中发生异常,Java虚拟机会将发生异常的相关信息输出到控制台,便于程序员对代码进行修正。如果没有异常机制,当程序由于异常而终止运行时,程序员很难判断程序中断执行的原因。异常机制可以使编写的程序更加健壮。
例子:通过异常情况,程序员可以修改代码,完善代码,使程序更健壮public class ExceptionTest05 {
public static void main(String[] args){
Student stu = null;
if(stu != null){
stu.print("张三", 20);
} else {
System.out.println("对象stu为空,没有指向任何引用!");
}
}
}
.
控制台显示:对象stu为空,没有指向任何引用!
例子:在代码中实现防止被零除
1.2Java中异常的分类
在Java中,采用类的形式模拟现实生活中的突发情况,如在上面的例子中,采用Java类java.lang.NumberFormatException来说明在程序运行过程中出现了数值类格式化异常,Java类java.lang.NullPointerException代表出现了空对象调用成员时出现的异常。
在Java中,每一类异常由一个特定的类来代表,如java.lang.NullPointerException异常类代表空指针异常类,但是需要注意的是,所有异常类的对象通常情况下不是由用户来创建的,而是在程序运行过程中,当出现了异常情况时,由Java虚拟机创建这个异常类型的对象赋给一个引用。这个异常对象就是一个具体的异常事件,在对象中保存了发生异常的详细信息。
1.2.1Java中异常类的继承关系
在Java中异常类的继承关系:
Throwable:异常类的父类
Error:错误,不可处理的异常,当发生Error时,程序直接退出,如:内存泄漏、断电
Exception:异常,必须进行处理的异常
RuntimeException:运行时异常,编译时不会提示,可控制的异常,不要求必须处理
编译时异常:程序编辑阶段就会提示的异常
1.2.2Java中异常的分类
在Java中,异常分为三类:错误、运行时异常(可控)和编译时异常(不可控)
1.错误,不可以处理的异常,当发生错误时,无法修复,直接退出运行,如内存溢出。
2.运行时异常,在编译期不会提示,如上面的例子,不要求必须进行处理的异常。所有RuntimeException类的子类都是运行时异常。运行时异常发生几率较低,因为这一类异常通常情况下只要完善代码就可以避免。
3.编译时异常,此类异常必须进行处理,在编译期就会提示,如果不处理,编译将无法通过。所有Exception类的子类都是编译时异常,除RuntimeException类及其子类。编译时异常发生几率较高。这一类异常在代码中是无法控制的,如:读取的文件不在指定的目录,要加载的类不存在等等,就是说这种类型的异常在代码中是无法解决的。
例子:编译时异常必须处理,否则编译不能通过public class ExceptionTest06 {
public static void main(String[] args){
//会提示要处理java.lang.ClassNotFoundException异常
//如果不处理异常,编译不能通过
Class.forName("java.lang.String");
}
}
1.3Java中异常的捕获与处理
对于Exception直接子类的异常类型必须进行处理,这一类异常称为编译时异常。运行时异常不需要在编译期进行处理。对于编译期异常的处理方式分为两种:
1.对可能存在的异常采取捕获并处理的方式
2.对异常不采取捕获并处理的方式,而是将可能发生的异常类型继续声明和抛出,让其他调用者捕获并处理。
1.3.1声明和抛出
方法可以通过throws关键字声明此方法可能出现的异常,格式如下:public void method()throws异常类型名称{…}
注意:声明的异常可以是运行时异常也可以是编译时异常,但是如果声明的是编译时异常,调用方法处必须对声明的异常进行处理,或者继续通过throws关键字进行声明抛出而不做处理,将异常继续向调用处进行传递。最终必须有一个调用处对异常进行处理。或者由Java虚拟机来处理。
例子:对于声明的异常是编译时异常,调用此方法的方法中必须对异常进行处理或继续对异常进行声明抛出
如果声明的是运行时异常,方法调用处不需要显示的对异常进行处理。下面通过例子说明。
例子:声明抛出的异常是运行时异常,调用此方法的方法中不需要对异常进行处理,因为这类异常发生率较低,并且可以通过优化代码避免异常的发生public class ExceptionTest07 {
public static void main(String[] args){
Test07 test = new Test07();
test.print();
}
}
class Test07{
public void print() throws RuntimeException{
System.out.println("测试运行时异常采用声明的方式");
}
}
通过throws命令将异常的处理权限向调用处进行传递,可以一级一级的向下传递,但是最终调用处要对异常进行处理,特殊的,如果主方法也同样声明且不处理,异常会被虚拟机处理。要注意运行时异常和编译时异常的区别,运行时异常对是否处理异常没有硬性的规定,所以不处理,程序也能通过编译。
例子:声明抛出的编译时异常调用处必须进行处理或继续抛出,否则编译不能通过public class ExceptionTest08 {
public static void main(String[] args){
Test08 test = new Test08();
//主方法没有声明异常,所以必须对异常进行处理,此处编译不能通过
//test.print();
}
}
class Test08{
//方法调用的print(String name)方法,同样也没有处理异常,
//采用throws关键字继续声明异常,将异常处理权向下一个调用处进行传递
public void print() throws Exception{
System.out.println("测试声明异常想调用处传递");
print("张三");
}
//此方法声明编译时异常,并没有处理异常,异常的处理权限向调用处进行传递
public void print(String name) throws Exception{
System.out.println("name: " + name);
}
}
例子:声明抛出运行时异常,调用处可以不处理,因为运行时异常可以通过代码修改避免异常的发生,是可控的异常
1.3.2异常的捕捉
采用try…catch方式对异常进行拦截并处理,语法格式为:try{
Java语句块;
}catch(异常类型1 e1){
处理语句块;
}catch(异常类型2 e2){
处理语句块;
}catch(异常类型3 e3){
处理语句块;
}
可以有多个catch语句块,用来拦截不同类型的异常。
当程序运行过程中发生不同类型的异常情况,执行不同类型异常的catch语句块代码,也就是catch语句块拦截相应类型的异常并执行相关的语句块。
例子:catch语句块可以有多个,用来拦截不同类型的异常事件
如果程序发生没有在catch中拦截的异常类型,此异常将不会被拦截。
例子:程序发生了没有在catch语句中指定运行时异常类型,不会被拦截,程序会终止运行,注意:运行时异常不是必须进行捕获并处理的异常
1.3.2.1拦截相同类型的异常事件
try语句块中的所有编译时异常必须在catch中进行拦截。如果存在没有拦截处理的编译时异常,程序编译不能通过。或者通过throws关键字进行声明抛出。
例子:编译时异常必须进行拦截或者通过throws进行声明抛出public class ExceptionTest09 {
public static void main(String[] args){
try{
Class.forName("abc");
FileInputStream in = new FileInputStream("abc.txt");
}catch(ClassNotFoundException e){
System.out.println(e);
}catch(FileNotFoundException e) {
}
System.out.println(“OK”);
}
}
1.3.2.2捕获异常的顺序
catch语句块中的异常类从上到下要按照子类到父类的顺序,否则编译不能通过。也可以采用父类的类型捕获子类类型的异常。
例子:catch语句的顺序是从子类到父类型,否则编译不能通过
1.3.2.3发生异常时的执行顺序
当try语句块中的代码发生异常情况时,程序的执行流程会直接执行拦截异常的catch语句块中的代码,当catch语句块中的代码执行结束后,会接着执行try…catch语句后面的语句,而不会接着发生异常的语句下面的语句接着执行。
例子:发生异常时,执行流程会发生改变,catch语句块相当于对异常进行了处理,所以执行流程会从try…catch后面的语句继续执行,而不会从发生异常的位置继续执行,所以不要将所有的语句都放在try语句块中public class ExceptionTest11 {
public static void main(String[] args){
try{
Class.forName("abc");
System.out.println("执行发生异常语句后面的语句!");
}catch(ClassNotFoundException e){
System.out.println("执行catch语句块中的代码!");
}
System.out.println("执行try...catch语句块后面的语句!");
}
}
控制台显示:执行catch语句块中的代码!
执行try...catch语句块后面的语句!
1.3.2.4异常类的实例
在catch语句块中异常类的实例e,是由虚拟机在发生异常时创建并将异常信息保存到这个异常实例中,通过这个异常实例可以获得程序发生异常的信息。
例12:异常信息保存在异常类型实例e中,通过e可以得到相关异常信息public class ExceptionTest12 {
public static void main(String[] args){
try{
Class.forName("abc");
}catch(ClassNotFoundException e){
System.out.println(e);
}
}
}
控制台显示:java.lang.ClassNotFoundException: abc
可以看出java.lang.ClassNotFoundException类或者他的父类重写了toString()方法,显示的不是内存地址,而是保存的异常信息。
1.3.2.5getMessage()方法
方法getMessage()方法来源于异常类的父类java.lang.Throwable,用于在控制台打印异常信息,但是显示的异常信息较为简单。
例13:异常对象的getMessage()方法给出的信息较为简单public class ExceptionTest13 {
public static void main(String[] args){
try{
FileInputStream in = new FileInputStream("abc");
}catch(FileNotFoundException e){
String msg = e.getMessage();
System.out.println(msg);
}
}
}
控制台显示:abc (系统找不到指定的文件。)
1.3.2.6printStackTrace()方法
方法printStackTrace()方法来源于异常类的父类java.lang.Throwable,用于在控制台打印发生异常的堆栈信息,是开发人员常用的方法,用来调试程序。
例14:异常对象的printStackTrace()方法给出堆栈中的信息,较为详细public class ExceptionTest14 {
public static void main(String[] args){
try{
FileInputStream in = new FileInputStream("abc");
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
}
控制台显示:java.io.FileNotFoundException: abc (系统找不到指定的文件。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at com.weixin.test1.ExceptionTest14.main(ExceptionTest14.java:11)
1.3.3finally语句块
在try…catch语句块后面可以添加finally语句块,语法格式如下:try{
语句块
}catch(Exception e){
语句块
}finally{
语句块
}
注意:finally语句块中的语句一定会执行,不考虑特殊情况。
例15:finally语句块中的语句一定会执行,主要作用是释放资源,在程序发生异常时,当catch语句块执行完毕后会接着执行finally语句块中的语句public class ExceptionTest15 {
public static void main(String[] args){
try{
Class.forName("abc");
System.out.println("发生异常的语句后面的语句不会执行!");
}catch(ClassNotFoundException e){
System.out.println("捕获异常的代码会执行!");
}finally{
System.out.println("finally语句块中的代码一定会执行!");
}
System.out.println("try...catch后面的语句会执行!");
}
}
控制台显示:捕获异常的代码会执行!
finally语句块中的代码一定会执行!
try...catch后面的语句会执行!
例16:当遇到return语句时,finally语句块中的语句也会执行,但try…catch后面的语句将不会执行,直接跳出当前方法的执行public class ExceptionTest16 {
public static void main(String[] args){
try{
System.out.println("程序正常执行!");
return;
}catch(Exception e){
System.out.println("捕获异常的代码不会执行!因为没有发生异常");
}finally{
System.out.println("finally语句块中的代码一定会执行!");
}
System.out.println("try...catch后面的语句不会执行!");
}
}
控制台显示:程序正常执行!
finally语句块中的代码一定会执行!
例17:当遇到System.exit(0)时,系统会直接退出运行,后面的代码都不会执行,包括finally语句块中的内容public class ExceptionTest17 {
public static void main(String[] args){
try{
System.out.println("程序正常执行!");
System.exit(0);
}catch(Exception e){
System.out.println("捕获异常的代码不会执行!因为没有发生异常");
}finally{
System.out.println("finally语句块中的代码不会执行!");
}
System.out.println("try...catch后面的语句不会执行!");
}
}
控制台不会执行finally语句块中的语句,因为虚拟机直接退出运行了。
1.4自定义异常
自定义异常可以是编译时异常也可以是运行是异常。在项目开发初期,根据项目的实际情况定义自己的异常类,如DaoException、ApplicationException,DaoException代表数据库操作阶段发生的异常,而ApplictionException是业务层发生的异常。定义自定义异常类较为简单,只要继承Exception或RuntimeException类并重写几个构造方法即可,下面通过例子说明。
例18:定义数据库相关操作的异常类public class DaoException extends Exception{
public DaoException() {
super();
}
public DaoException(String message) {
super(message);
}
}
例19:定义业务层的异常类public class ApplicationException extends Exception{
public ApplicationException() {
super();
}
public ApplicationException(String message) {
super(message);
}
}
1.4.1通过throw关键字手动抛出异常
可以通过throw关键字手动抛出异常,在手动抛出异常的方法中要显示的通过throws关键字声明异常,通知调用者处理或继续声明异常。手动抛出的编译时异常调用者必须进行处理或继续声明。而运行期异常调用者可以不进行处理。
例20:public class ExceptionTest18 {
public static void main(String[] args){
Test18 t = new Test18();
try {
t.print();
} catch (ApplicationException e) {
e.printStackTrace();
}
}
}
class Test18{
public void print() throws ApplicationException{
FileInputStream in = null;
try{
in = new FileInputStream("abc");
}catch(FileNotFoundException e){
throw new ApplicationException("访问的文件不存在!");
}finally{
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("finally语句块中的代码会执行!");
}
System.out.println("发生异常后,try语句块后面的语句不会执行!");
}
}
控制台显示:finally语句块中的代码会执行!
com.weixin.test1.ApplicationException: 访问的文件不存在!
at com.weixin.test1.Test18.print(ExceptionTest18.java:27)
at com.weixin.test1.ExceptionTest18.main(ExceptionTest18.java:13)
注意:手动抛出异常必须在catch代码块的最后一行,并且发生异常语句后面的语句不会执行,但finally语句块会执行。