子曰:“温故而知新,能够为师矣”。的确是这样,对于技术知识的学习,我深有感悟。每一本书,每个知识点,不去认真的读上个2~3遍,根本没法理解其中的道理。借着最近在学习SSH框架的机会,也抽时间把Java基础知识好好再总结一遍,再系统的经过博文的形式将相关的知识树,知识模块总结出来。一来作到查缺补漏;二来从总体上再好好把握Java的基础脉络。java
写过C/C++的程序员无不佩服Java中的异常处理,功能强大不说,并且能够作到傻瓜式入门。想一想当初写Java的时候,时不时就加上这么一段,管它对不对,反正是好的:程序员
try { // 可能会发生异常的程序代码 } catch (Exception ex) { // 捕获并处理try抛出的异常类型ex } finally { // 不管是否发生异常,都将执行的语句块 }
就是这样无头无脑的用,可是这背后的东西并无过多的去了解和学习,直至有一天发现,我须要去更多的学习一下Java的异常。先从Java异常的类体系提及,以下图所示:数据库
上图就是咱们常用的一些类的继承结构图。这是一幅很是普通的类继承结构图,经过以后的学习,咱们能够将上图继续总结,从而概括为下面这样:数组
在Java中异常被当作对象来处理,根类是java.lang.Throwable
类,全部异常类都必须直接或间接继承自Throwable
类,Throwable
类分为如下两个子类:框架
Error
类,它是error类型异常的父类;error类型异常是程序没法处理的异常。通常发生这种异常时,JVM会选择终止程序,所以在咱们编写程序时,并不须要关心error类异常Exception
类,它是exception类型异常的父类,这类异常就是咱们编码时须要注意的异常。而对于Exception
类,它又分为如下两大类:学习
NullPointerException
、IndexOutOfBoundsException
、IllegalArgumentException
等IOException
、SQLException
等关于RuntimeException
和非RuntimeException
,下面再进行详细的总结。编码
上面说到Exception
类能够分为RuntimeException类和非RuntimeException类。那么具体来讲,何为RuntimeException类异常?何又为非RuntimeException类异常呢?设计
RuntimeException类异常,又被称为Unchecked Exception(非检查异常);Unchecked表示在编译阶段,Java编译器不要求必须进行异常捕获处理或者抛出声明,而要到运行期间才能由JVM去决定是否要抛出这类异常。这些异常通常是由程序逻辑错误引发的,程序应该从逻辑角度尽量避免这类异常的发生。须要注意的是,Error
类错误也是属于RuntimeException类异常。例以下面这段代码:调试
public class Test { public static void main(String[] args) { int[] intArray = { 1, 2, 3, 4, 5 }; System.out.println(intArray[7]); // 越界访问数组 } }
很明显的,我越界访问数组了;可是在编译的时候,这并无问题,直到运行时,才会抛出java.lang.ArrayIndexOutOfBoundsException
异常。日志
非RuntimeException类异常,又被称为Checked Exceptions(检查异常);这类异常表示在编译阶段,编译器会检查此类异常,若是代码中有明确标明抛出此类异常的代码,而却没有try...catch...
这类异常处理结构,此时就会出现编译错误。例以下面这段代码:
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; public class Test { public static void main(String[] args) { File file = new File("c:/city.xml"); BufferedReader reader = null; reader = new BufferedReader(new FileReader(file)); } }
因为new FileReader(file)
的声明中明确代表了会抛出java.io.FileNotFoundException
异常,而我在代码中却没有明确的try...catch...
代码块,致使在编译的时候,上面这段代码都没法经过。
这就是所谓的RuntimeException类和非RuntimeException类异常。
在编码的时候,必不可少的要写各类try...catch...finally...
。虽然写的很多,基本上都是千篇一概,并无真正的考虑过如何把这个try...catch...finally...
写好,所以养成了如下这些毛病。
直接打印异常信息到页面上
你们可能都见过这样的状况,访问某个JSP页面出错了之后,在页面上显示一堆的错误堆栈信息。这些信息对于开发人员来讲是很是重要的,可是对于用户来讲就是“天书”。为了不这种状况的发生,咱们在编码时就须要格外的注意,逻辑处理要正确,防止出现非检查异常。
异常耦合
咱们在开发的时候,都是分模块的,好比最流行的MVC结构,它要求各个部分应尽最大可能的解耦。可是不尽人意的是,若是处理很差异常处理代码,则会增长模块之间的耦合度。例如如下这段代码:
public UserInfo getUserInfoById(String id) throw SQLException { // 根据ID查询数据库,获取用户信息 }
上面这段代码咋一看没什么问题,可是从设计耦合角度仔细考虑一下,这里的SQLException
污染到了上层调用代码,调用层须要显式的利用try...catch...
捕捉,或者向更上层次进一步抛出。根据设计隔离原则,咱们能够适当修改为:
public UserInfo getUserInfoById(String id) { try{ // 根据ID查询数据库,获取用户信息 } catch(SQLException e){ //利用非检测异常封装检测异常,下降层次耦合 throw new RuntimeException(ErrorCode, e); } finally{ //关闭链接,清理资源 } }
忽略异常
咱们常常写出这样的代码:
public UserInfo getUserInfoById(String id) { try{ // 根据ID查询数据库,获取用户信息 } catch(SQLException e){ // 打印错误信息 ex.printStacktrace(); } }
咱们捕获了异常之后,就打印了异常堆栈信息;对于开发调试来讲,还有用处,可是对于生产环境来讲,咱们须要将异常信息写到日志文件,而此处只是打印了异常,并无处理,接下来程序还会继续执行,这样就会进一步致使更严重的问题。因此,在代码中这种简单的打印错误,却忽略了正确的处理异常。
利用Exception
捕捉全部潜在的异常
咱们也常常写出这样的代码:
public UserInfo getUserInfoById(String id) { try{ // 根据ID查询数据库,获取用户信息 } catch(Exception e){ // 打印错误信息 throw new RuntimeException(ErrorCode, e); } }
有的时候,咱们会为了偷懒,就使用一个Exception
来捕获全部的异常;可是若是这样,若是真的出现了异常,咱们就没法准确的定位具体的异常,从而为解决问题增长了难度。对此,咱们就须要对抛出的异常进行细分,单独处理每一个异常。
屡次打印异常
有些时候,咱们编写代码很谨慎,一旦出现了异常就赶忙捕获异常,记录异常堆栈,好比这样的[代码][3]:
class A { private static Logger logger = LoggerFactory.getLogger(A.class); public void process(){ try{ //实例化 B 类,能够换成其它注入等方式 B b = new B(); b.process(); //other code might cause exception } catch(XXXException e){ //若是 B 类 process 方法抛出异常,异常会在 B 类中被打印,在这里也会被打印,从而会打印 2 次 logger.error(e); throw new RuntimeException(/* 错误代码 */ errorCode, /*异常信息*/msg, e); } } } class B{ private static Logger logger = LoggerFactory.getLogger(B.class); public void process(){ try{ //可能抛出异常的代码 } catch(XXXException e){ logger.error(e); throw new RuntimeException(/* 错误代码 */ errorCode, /*异常信息*/msg, e); } } }
经过仔细的阅读代码,咱们就会发现会屡次记录异常堆栈信息,不只带来效率问题,同时也会记录了不少的冗余信息,给问题定位带来了麻烦;若是调用层次更多一些的话,那更不敢想象会记录多少异常堆栈。
除了上面这些坏习惯,咱们在编码时还要注意不少,不少,这里就不一一举例了。
说的再多,咱们终究须要在咱们的代码中使用异常,那么如何设计一个简单、易用的异常处理框架呢?当设计一个异常处理框架时,须要基于如下几点去考虑:
基于上面几点进行考虑,之后在阅读开源代码时,也能够基于上述几点去分析。
基础知识的重要性不言而喻,只有明白了其中的原因,在编码时才能作到心中有数。但愿这篇文章对你们有帮助。
2015年12月22日 于呼和浩特。