大家好,欢迎来到IT知识分享网。
一、前言
本文主要讲述异常处理的机制和原理,以及探讨开发Java程序时,每一层如何进行异常处理?异常何时被抛出?何时被记录?如何记录?记录checked异常还是unChecked异常?异常是否应该呈现到前端页面?如何设计一个异常框架等问题。
二、基本概念
异常是程序在运行时出现的不正常情况。是Java按照面向对象的思想将问题进行对象封装。这样就方便于操作问题以及处理问题。
异常处理的目的是提高程序的健壮性。你可以在catch和finally代码块中给程序一个修正机会,使得程序不因不可控制的异常而影响程序的流程。同时,通过获取Java异常信息,也为程序的开发维护提供了方便。
**Java异常类层次结构图**
-
Java中的异常用对象来处理,并定义java.lang.Throwable作为所有异常的超类。Throwable分成了两个不同的分支,Exception(异常)和 Error(错误);
-
其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常。或不受检查异常(Unchecked Exception)和检查异常(Checked Exception);
-
异常是针对 方法 来说的,抛出、声明抛出、捕获和处理异常都是在方法中进行的;
-
Java异常处理通过5个关键字try、catch、throw、throws、finally进行管理;
-
Error(错误):灾难性的致命的错误,是程序无法控制和处理的。
Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误、内存溢出。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误、链接错误。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
-
Exception(异常):通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。。
-
运行时异常和受检查异常
-
运行时异常 (unChecked异常):
RuntimeException类及其子类都被称为运行时异常。这些异常一般是由程序逻辑错误引起的,属于应该解决的Bug,程序应该从逻辑角度避免这类异常的发生,不推荐try-catch来捕获处理,但是有时候为了增强用户体验,保证Crash次数降到最低,会人为捕捉一些运行时异常。这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try-catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。但在运行时会被系统自动抛出。
-
非运行时异常 (checked异常):
除了RuntimeException类及其子类外,其他的Exception类及其子类都属于非运行时异常,从程序语法角度讲是必须进行处理的异常,如果不处理程序就不能编译通过。
-
-
异常转型和异常链:
我们做的JEE项目时候,一般会有三层的结构:持久层、逻辑层、展现层。异常也是如此的,当我们各个层之间传递异常,我们就需要先封装,然后传递。
- 异常链示例
catch (SQLException e)
{
throw new JdbcException(e);
}
三、异常处理机制
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
- 抛出异常
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
该方法的调用者必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,所经方法都层层上抛获取的异常,若最终都没有被处理,将交由虚拟机处理。处理也很简单,就是打印异常消息和堆栈信息,记录日志。
- 捕捉异常
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
在方法中用try-catch语句捕获并处理异常,catach语句可以有多个,用来匹配处理异常。并且尽量将捕获底层异常类的catch子句放在前面。
异常总是先被抛出,后被捕捉的。
- Java规定
对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
RuntimeException由业务逻辑保证。
3.1、抛出异常实例(throws 和 throw)
public class Throws {
public static void main(String[] args) throws Exception{
//抛出异常类
System.out.println(10 / 0);
throw new Exception("抛出异常对象");
//System.out.println("throw后面的代码不再执行");
}
}
3.2、捕获异常实例(try-catch 和 finally)
import java.util.InputMismatchException;
import java.util.Scanner;
public class TryCatch {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
//可能会发生异常的程序代码
System.out.print("请输入一个数字:");
int a = input.nextInt();
} catch (InputMismatchException e) {
// 捕捉异常
System.err.println("数据类型不符!");
e.printStackTrace();
System.err.println(e.getMessage());
// return; 若捕捉到异常会先执行finally, 再return。
} catch (Exception e) {
// catch 先写子类,再写父类
System.out.println("再捕捉一次!");
// System.exit(0);
} finally {
// 除非执行System.exit(0),否则都会执行
System.out.println("finally 被执行!");
// 应用举例:确保关闭数据库,关闭流
}
System.out.println("我还是被执行了!");
// 如果提前return,则不执行了
}
}
- throw 和throws关键字的区别
- throw用于抛出异常对象,后面跟的是异常对象;throw用在方法内。
- throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在方法方法签名上。
- 通常情况:方法内容如果有throw,抛出异常对象,并没有进行处理,那么方法上一定要声明,否则编译失败。
四、Java常见异常
4.1、Error
- LinkageError:链接错误;
- ThreadDeath:线程死锁;
- OutOfMemoryError:内存溢出;
- StackOverflowError :堆栈溢出;
- NoClassDefFoundError:类定义错误;
- Virtual MachineError:虚拟机运行错误。
4.2、运行时异常(unChecked异常)
- SecurityException:安全性异常;
- NullPointerException:空指针异常;
- ClassCastException:类型强制转换异常;
- ClassNotFoundException:找不到类异常;
- IllegalArgumentException:非法参数异常;
- NegativeArraySizeException:数组长度为负异常;
- ArithmeticException:算术条件异常。如:整数除零;
- ArrayIndexOutOfBoundsException:数组下标越界异常;
- ArrayStoreException:数组中包含不兼容的值抛出的异常;
- StringIndexOutOfBoundsException:字符串下标越界异常;
- ArrayStoreException:向数组中存放与声明类型不兼容对象异常;
4.3、非运行时异常(checked异常)
- IOException:输入输出流异常;
- SQLException:数据库操作异常;
- EOFException:文件已结束异常;
- TimeoutException:执行超时异常;
- DataFormatException:数据格式化异常;
- NoSuchFieldException:没有匹配的属性异常;
- ClassNotFoundException:没有匹配的类异常;
- FileNotFoundException:没有匹配的文件异常;
- NoSuchMethodException:没有匹配的方法异常;
4.4、Throwable类的主要方法
- public String getMessage():返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
- public Throwable getCause():返回一个Throwable 对象代表异常原因。
- public String toString():使用getMessage()的结果返回类的串级名字。
- public void printStackTrace():打印toString()结果和栈层次到System.err,即错误输出流。
- public StackTraceElement [] getStackTrace():返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
- public Throwable fillInStackTrace():用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。
五、自定义异常实例
- 实例一:
class UserException extends Exception {
// 继承父类
public UserException() {
super();
}
public UserException(String message) {
super(message);
}
}
public void activatioin(String code) throws UserException {
try {
User user = userDao.findByCode(code);
if(user == null) throw new UserException("无效的激活码!");
if(user.isStatus()) throw new UserException("您已经激活过了");
userDao.updateStatus(user.getUid(), true); // 修改状态
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
- 实例二:
package Test;
import java.lang.Exception;
public class TestException {
static int quotient(int x, int y) throws MyException {
// 定义方法抛出异常
if (y < 0) {
// 判断参数是否小于0
throw new MyException("除数不能是负数"); // 异常信息
}
return x/y; // 返回值
}
public static void main(String args[]) {
// 主方法
int a =3;
int b =0;
try {
// try语句包含可能发生异常的语句
int result = quotient(a, b); // 调用方法quotient()
} catch (MyException e) {
// 处理自定义异常
System.out.println(e.getMessage()); // 输出异常信息
} catch (ArithmeticException e) {
// 处理ArithmeticException异常
System.out.println("除数不能为0"); // 输出提示信息
} catch (Exception e) {
// 处理其他异常
System.out.println("程序发生了其他的异常"); // 输出提示信息
}
}
}
class MyException extends Exception {
// 创建自定义异常类
String message; // 定义String类型变量
public MyException(String ErrorMessagr) {
// 父类方法
message = ErrorMessagr;
}
public String getMessage() {
// 覆盖getMessage()方法
return message;
}
}
六、Java异常处理的原则和技巧
-
不要把自己能处理的异常抛给别人;
-
catch块尽量保持一个块捕获一类异常;
-
细化异常的类型,不要不管什么类型的异常都写成Excetpion;
-
避免过大的try块,不要把不会出现异常的代码放到try块里面;
-
如果把父类的异常放到前面,后面的catch语句块将得不到执行的机会;
-
尽量将异常统一抛给上层调用者,由上层调用者统一决定如何进行处理。
-
不要用try-catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况;
-
只要不是retry或者queue的情况,基本上所有的异常都是需要继续向上抛的,最终交给顶层异常处理机制(应用或者容器)。
-
Java异常处理三原则
-
具体明确
-
提早抛出
- 通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。
-
延迟捕获
- 异常发生时,不应立即捕获,而是应该考虑当前作用域是否有有能力处理这一异常的能力,如果没有,则应将该异常继续向上抛出,交由更上层的作用域来处理。
-
-
如何记录异常(写入日志)
- 在异常最开始发生的地方进行日志信息记录;
- 如果捕获到一个异常,但是这个异常是可以处理的。则无须记录异常;
- 捕获到一个未记录过的异常或外部系统异常时,应该记录异常的详细信息。
-
记录checked异常还是unChecked异常
- 如果一个异常是可以恢复的,可以被调用者正确处理的,使用checked异常;
- 如果一个异常是致命的,不可恢复的。或者调用者去捕获它没有任何益处,使用unChecked异常;
- 在使用unChecked异常时,必须在在方法声明中详细的说明该方法可能会抛出的unChekced异常。由调用者自己去决定是否捕获unChecked异常;
- 受检异常尽可能转化为非受检异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-368cxLQh-1652075258122)(https://img-my.csdn.net/uploads/201710/06/1507224495_1036.png)]
-
在类继承的时候,方法覆盖时如何进行异常处理
- 如果父类的方法声明一个异常,则子类在重时声明的异常范围应该不小于 父类;
- 如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws;
- 如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。
七、异常的转换与异常链
七、异常统一处理
一个好的异常处理框架能为应用程序的异常处理提供统一的日志记录、异常通知、处理视图,把异常处理从程序正常运行逻辑分离出来,提供更加结构化以及可读性的系统架构。另外,一个好的异常处理框架具备可扩展性,很容易根据具体的异常处理需求,扩展出特定的异常处理逻辑。
通常的异常处理模式包括业务委托模式(Business Delegate)、前端控制器模式(Front Controller)、拦截过滤器模式(Intercepting Filter)、AOP 模式、模板方法模式等。
七、拓展阅读
八、参考资料
九、后记
未完待续
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/25378.html