1.什么是Java异常?
今天我们来聊聊java异常,异常时导致程序中断执行的一种指令流。我们在提高代码稳定性和健壮性的时候,常常会花更多的时间去考虑,代码可能存在的异常。编码中对可能的发生的异常先一步正确处理,就可以确保异常不会导致程序不可用。说了这么多异常,那么异常是什么呢?
Java异常是一种导致程序中断的执行指令流。它存在应用服务器硬件错误、程序编写的时候未考虑数组越界、也可能是网络通信中网络抖动的客观原因导致的异常等情况。异常可能是主观代码设计不周全也可能是客观硬件报错,那么认识异常就是避免异常的第一步。我们先从异常家族的认识开始,下面异常的族谱:
Throwable类作为所有异常的发源,他是整个Java异常体系的超体,其下面分为Error和Exception两大类。
Error与其子类实例代表严重系统错误,应用程序无法处理,如硬件层面的错误、JVM错误或内存不足等问题,这种错误发生时Java应用程序本身无力恢复。Error对象抛出时,基本上不用处理,任其传播至JVM为止,应用程序能做的最多留下日志信息。
Exception代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。Exception及其子类可以被程序处理,也就是所在应用程序中正确的处理了Exception及其子类就能提高代码的稳定性和健壮性。
Exception类下面分为检测异常和非检测异常:
检测异常,是JVM强制要求程序员为可能出现的异常做预备处理工作。JVM规定检测异常要么使用try-catch语句捕获它并进行处理,要么使用throws子句声明并抛出它,否者javac在编译程序会不通过。这类异常一般是程序运行环境导致的,为了预防未知环境对程序的影响,规定程序员需要处理这类异常。
非检测异常,javac在编译时,不会提示和发现异常的存在,JVM不强制要求程序员处理这样的异常。当然,作为程序员我们应该预防这样的异常导致程序崩溃,所以建议使用try-catch-finally处理它。这类异常原因多半是代码写的逻辑有问题或考虑不周全。
2.当一个Exception在程序中发生的时候,JVM是怎么做的呢?
对java整体异常家族有了大致的认识之后,那么当一个Exception在程序中发生的时候,JVM是怎么做的呢?
认识这个问题,我们首看看JVM是如何执行java代码的,按照执行步骤可以分为,JVM将class文件转换为JVM的java类。JVM为java类建立方法表,方法表中的索引可以引导JVM找到需要执行的方法体和JVM方法执行栈。当然,invokemethod调用执行一个方法的时候,Java虚拟机把描述该方法的栈结构置入方法执行栈栈顶,位于栈顶的方法为正在执行的方法。JVM会为每一个方法建立执行的堆和栈用于存放执行中的变量,如果此时程序出现一个Exception,抛出的异常先转移给合适的异常处理语句。代码的执行会被相应的Exception执行流接管,将方法表信息、发生异常的位置信息和方法的堆栈信息压入Exception的堆栈,当程序中断的时候IDE就会将Exception的堆栈信息打印出来。
如果我们通过try…..catch….finally语句来处理异常,处理流程又是怎样的呢?在try代码块中抛出的异常,代码执行流会跳转到catch代码块执行,catch代码块可以对发生的异常进行补救。让代码回归到正常的执行流程。finally语句,无论在try模块中是否发生异常,都会执行finally语句,使用finally语句主要是为了释放被占用的资源,比如打开的文件或链接的通信资源等。
总体来说,Java语言的异常处理流程,从程序中获取异常信息。根据Java的源文件和用户调用的包列表,JVM可以获取该程序包括Java API所引发的异常在内的异常处理的信息,这些信息包括:异常引发位置、异常抛出顺序、引发异常的方法名和类名等。这些异常信息在程序异常的排查和修改的时候非常重要,后面会讲到如何正确处理这些异常。
3.当我们编写程序的时候如何对待可能出现的异常呢?
通常在发生异常的时候我们有两种处理模型:终止与恢复。
终止模型:前提是假设错误非常关键,以至于程序无法返回到程序正常运行轨迹,一旦异常抛出就意味着程序将停止提供服务。如:数据库连接异常发生
恢复模型:也就是异常程序发生错误,错误可以被修复然后重新回到正常程序执行的轨迹上。例如,我们可以将数据库连接try。。。catch置于循环中,一次连接不成功可以循环下一次进行连接。
配合终止和恢复模型,我们会配合使用throw和throws语法。throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常。throws 可以理解成是一种通知行为,没有实际的抛出异常的动作,而仅仅是告诉调用他的上层函数,这里可能会抛出这个异常;
throw用于在函数体内语句中,表示抛出一个实际的异常的实际动作,如果在函数内没有捕获并处理,那么将会一直向上抛出这个异常直到被main()/Thread.run()抛出。
我们了解了Java的异常内容之后,在程序中遵循怎样的行业规则和大牛的经验合理使用异常,提高代码的稳定性呢?
下面整理了包括Effective Java异常使用指导原则和网上博客内容:
1. 不要讲异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)。
2. 对可以恢复的情况使用了受检异常,对编程错误使用运行时异常。
3. 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)。
4. 优先使用标准异常。
5. 每个方法抛出的异常都要有文档。
6. 保持异常的原子性。
7. 不要再catch中忽略掉捕获到的异常。
8. 处理运行时异常,采用逻辑合理规避同时辅助try…catch处理。
9. 在多重catch快后面,可以加一个catch(Exception)来处理可能会被遗漏的异常。
10. 对于不确定的代码,也可以加上try…catch,处理潜在的异常。
11. 尽量去处理异常,切忌只是简单的调用printStackTrace()打印输出。
12. 具体如何处理异常,要根据不同的业务需求和异常类型去决定。
13. 尽量添加finally语句块去释放占用的资源。
不要被这么多规则吓到了,不需要逐条强记他们。我对异常的理解,首先态度上要谦虚明白代码中很难避免因为考虑不周出现的异常,所以编码时应该采用防卫式编码。其次在编码处理过程中使用try…catch…finally语句预防可能的异常,采用合适的异常处理模式对待程序可能的异常。
网友评论