异常设计原则与继承体系

异常

异常体系

在这里插入图片描述

Error与Exception

  • Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath、NoClassDefFoundError等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
  • Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常,使程序继续正常运行。

Exception

  • 运行时异常
    • 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
    • 这些异常一般是由程序逻辑错误引起,程序应从逻辑角度尽可能避免这类异常的发生,一旦发生程序一般没有按照错误逻辑运行下去的必要。
  • 非运行时异常
    • 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
    • 受检异常一般是与程序与外界交互失败才会抛出,希望可以处理使程序正常运行。

Throwable类中的常用方法

  • getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。
  • getMessage():返回异常的消息信息。
  • printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。

try、catch、finally三个语句块应注意的问题

  • 第一、try、catch、finally三个语句块均不能单独使用,三者可以组成 try…catch…finally、try…catch、
    try…finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
  • 第二、try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。
    如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
  • 第三、多个catch块时候,只会匹配其中一个异常类并执行catch块代码,而不会再执行别的catch块,
    并且匹配catch语句的顺序是由上到下。

throw、throws关键字

  • throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。
    • 如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。
    • 如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。
  • throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。

异常处理的一般原则

  • 能处理就早处理。因为对于一个应用系统来说,抛出大量异常是有问题的,应该从程序开发角度尽可能的控制异常发生的可能。
  • 异常能处理就处理,不能处理就抛出,不要catch后不处理,最终没有处理的异常JVM会进行处理
  • 对于检查异常,throws还是catch,应该根据具体业务场景决定,如果希望上层知道则应throws。
  • 对于一个应用系统来说,如果不能有效的处理受检异常,可以转译转换为更加符合业务场景的RuntimeException抛出。上层的代码决定是否处理。
  • 对于一个应用系统来说,应该有自己的一套异常规范,当异常发生时,可以是统一的处理风格,将优雅的异常信息反馈给用户。
系统异常设计原则

理论:异常转译

将一种异常转换另一种新的异常,也许这种新的异常更能准确表达程序发生异常。在JDK中也是,几乎所有带异常原因的异常构造方法,都使用Throwable类型做参数将其包装一层实现转译,这也就为异常的转译提供了直接的支持。比如将SQLException转换为另外一个新的异常DAOException。

理论:异常传播(异常链)

将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。

try {
    lowLevelOp();
} catch (LowLevelException le) {
    throw new HighLevelException(le)
}
  • 在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。这样异常的原因就会逐层传递。这样,位于高层的异常递归调用getCause()方法,就可以遍历各层的异常原因。
  • 异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,因为要保存一个完整的异常链信息

对外统一暴露异常基类

系统异常的基类,对外以及对上层只抛出这个异常的子类,建立统一的异常抛出体系,从而可以更加准确的根据业务场景转译异常。同时实现了对外暴露统一的异常,可以使用切面拦截处理,或者抛给前端接收处理,因为都是同一种异常,所以可以将"友好"的信息展示给客户。

有关异常框架设计这方面公认比较好的就是Spring,Spring中的所有异常都可以用org.springframework.core.NestedRuntimeException来表示,其继承的是RuntimeException。同时也会给每个抛出的自定义运行时异常,在其方法签名上throws然后用注释说明。Spring框架很庞大,因此设计了很多NestedRuntimeException的子类,还有异常转换的工具

补充

什么时候捕获异常,捕获之后怎么处理

受检异常时,能处理就处理,处理不了转译为系统异常体系的符合当前场景的运行时异常往上抛,然后在方法签名上throws该异常,并加注释说明,让上层代码决定是否处理

classnotfoundexception和noclassdeffounderror

主要还是类找不到的时机不同,一个是通过反射调用加载类,一个是jvm由于链接时需要类加载找不到

classnotfoundexception:

  • JDK中的解释是:当应用程序视图使用以下方法通过字符串加载类时,没有在classpath中查找到执行的类,那么就会抛出ClassNotFoundException。
    (1)Class类中的forName()方法
    (2)ClassLoader类中的findSystemClass方法
    (3)ClassLoader类中的loadClass方法。
  • 还有根据类加载器的可见性机制,子类加载器可以看到父类加载器加载的类,而反之则不行。所以当一个类已经被子类加载器加载,再用父类加载器加载时,将会抛出java.lang.ClassNotFoundException异常。

noclassdeffounderror:

要找的类在编译时期还可以找到,但是在运行java应用的时候找不到了,这比较经常出现在静态块的初始化过程中。JDK官方的解释:当虚拟机或ClassLoader实例在类的定义中加载(作为通用方法调用的一部分或者作为new 表达式创建的新实例的一部分),但是无法找到该类的定义时,抛出此异常。当前执行的类被编译时,所搜索的类定义存在,但无法再找到该定义。

ClassNotFountException异常,怎么解决,分析为什么会出现

检查全限定类名

检查环境变量classpath的配置,看一看所需要的支持类库是否放在classpath路径里面

检查是否使用了重复的类库,且版本不一致,导致错误版本被优先使用

  • jar包冲突导致类找不到(ClassNotFountException、noclassdeffounderror):有两个不同版本的jar包,在maven仲裁时选择了错误的版本,导致没有该类
  • jar包不冲突,但是有两个相同全限定类名的类在不同jar包,导致了NoSuchMethod:因为jvm类加载机制,先加载上层类路径,还有同层因为class文件排序不同,导致加载了错误的类。
  • 解决办法,用<dependence mangement>做好版本管理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值