异常

异常就是程序生病了,不处理异常,程序就会翘翘,终止运行。对于异常的方法,一定要加上 @exception 文档注释。子类在重写父类中的抽象方法时处理的异常一定要比父类中处理的异常多,也就是说子类 throws 的异常一定只能比父类 throws 的异常少。说的更直白点,就是儿子一定不能比老子懒,再不济也要一样勤快。只要这样世界才会不断进步呢!

异常通常都是交给控制层来处理的,业务层和持久层专注于功能实现,在框架中甚至都不需要自行在控制层手工处理异常了。这是因为异常的设计之初就是为了帮助程序员方便处理错误,所以对待异常的原则应该是把出现问题和处理问题的地方分隔开来。业务层和持久层专注于处理业务和数据存储的问题,异常就全部抛给控制层去处理。

为什么要引入异常机制?

首先明确一个大前提,运行中的程序是一定无法避免出现问题的。比如断电了或者机器硬件坏了,文件找不到或者断网了。那怎么办呢? Java 就提供了异常机制来处理这类问题,通过异常机制来保证程序的健壮性和可维护性。

Java 提供的异常机制

问题分为 Error 和 Exception ,比如断电了或者机器硬件坏了,导致程序无法正常运行,这类属于 Error ,再厉害的程序员也不可能通过写代码解决。比如文件找不到或者断网了,导致程序无法正常运行,这类属于 Exception 。Exception 又分为编译异常和运行异常。编译异常通常给用户一个良好提示,运行异常在编写源代码的过程中要尽量解决掉。异常的命名应该是能够望文生义的,看到名字就能够猜出异常它的作用,无论是 java 标准类库还是自定义的异常类都应该遵循此规则,所有东西都应该是这样命名的啊。比如所有的输入输出异常都是 IOException 的子类!

java.lang.Throwable

throwable 是一个形容词,表示可抛出的意思。继承于 java.lang.Object 类,是 Error 和 Exception 的直接父类,它提供了异常类的基本功能。异常类继承层次结构图如下:

_Throwable.png

既然所以异常类都是继承自 Throwable ,那么 Throwable 拥有的数据结构对于每一个异常类都是存在的。对于异常类需要知道的数据结构有以下几个点。

  • private String detailMessage私有的 String 类型的属性,detailMessage 属性是用来描述异常的一段字符串信息。
  • 至少两个构造函数,一个空构造函数,一个有参的构造函数来给私有属性 detailMessage 赋值。
  • public String getMessage()方法,返回的就是异常类的 detailMessage 属性。
  • public String toString()Throwable 重写了其父类 Object 的 toString() 方法,其输出格式为:异常类名:detailMessage
  • public void printStackTrace()用来打印异常栈的信息,方便跟踪程序异常信息。控制台中打印异常栈信息的顺序是:先从异常抛出处的方法开始,一路回退到它最开始的调用方。为什么是这样子的呢?要明白,任何知识点都不是孤立的,他们之间一定能够建立联系。实现这个功能其实很简单啊,每一个线程都有一个方法调用栈,所以在遇到抛出异常方法的时候,在这个方法栈里面已经积压了很多方法了,边退栈边打印调用栈中方法的信息即可。理解了这一点,就理解了为什么会先从抛出异常方法处首先打印异常了,这不就是退栈的过程吗?此方法还有另外两个重载的方法,区别在于不同的参数,接收不同的输出流对象,如下public void printStackTrace(PrintWriter s)public void printStackTrace(PrintStream s)方法

编译异常

5编译异常中最常见的就是 IOException 了,这里也只拿这类异常举例。编译异常必须显示的使用 try catch 进行预处理,否则编译阶段就不给通过。

这其实是 java 的一种设计思想,因为程序运行过程中发生资源不存在问题的可能性非常大,所以 JDK 类库设计者提供的某些方法或者构造方法就显示的使用 throws 关键字事先声明不处理某种异常,强迫调用这种方法的客户端程序员对这类异常进行预处理。

运行异常

运行异常是在程序运行过程中遇到不正常情况,由 JVM 创建并抛出的异常对象,如果没有对此异常进行处理的话,该异常对象一路被抛到 main() 方法中,JVM 就会自动调用该异常对象继承子 Throwable 的public void printStackTrace()方法打印异常栈信息。这种运行时异常是无法在编译阶段检查出来的,因为它完全符合语法规则,只有在程序运行时,JVM 才能够判断它是否会出现问题。

比如NullPointerExceptionArithmeticException就是常见的运行时异常类。运行时异常才是真正让人感觉到可怕的事情,在编写程序的过程中,即使语法上不要求进行异常处理,但是最好显示的去判断,去处理,程序编写可能显得比较麻烦,但是真正出问题了,就会发现一切付出都是值得的。

异常的处理流程

这里拿运行异常举例,编译异常是同理的。程序运行过程中,JVM 发现不正确情况,就在 new 出一个异常对象,并给它的 detailMessage 属性赋值。然后检查此处是否有 try catch 捕获了对应的异常对象,如果有则进入到 catch 代码段,如果没有则查看此方法是否使用 throws 关键字声明不处理异常,如果有则到调用此方法的方法中进行同样的流程处理。如果都没有,JVM 就会抛出此异常,程序被迫中断,控制台打印出相应的异常信息。

捕获异常的原则是必须要尽可能的细化,catch 代码块要呈金字塔铺开。做更细致化的异常处理是为了分化问题,便于对具体问题做具体分析和处理。要想成为一个好的程序员,一定要做到这些。

如果在主方法中使用 throws 声明不处理异常,这只是骗了编译器,语法上是通过了,但是主方法是 JVM 调用的,相当于还是抛给了 JVM ,该发生的异常还是会发生,程序该中断还是会中断。

throws 和 throw 以及 finally

throws 用在方法声明后面,后面跟的是一个或多个异常类,表示不处理异常。throw 用在方法内部,后面跟的是一个异常对象,表明此处抛出一个异常对象。如果一个方法中 throw 出一个异常对象,此方法就必须用 throws 声明不处理此异常,那就会抛给调用此方法的方法去处理。或者在此方法内部用 try catch 捕获,否则编译阶段都不会通过。

至于 finally 关键字,设计之初的本意是用来关闭资源的,比如输入输出流发生异常,在 finally 代码块中关闭资源。无论程序是否发生异常,finally 的代码都会被执行,这是 Java 的设计机制。

try 中发生异常,如果异常被 catch 捕获,则先执行catch 的语句,再执行 finally 的语句。如果异常没有被捕获,会先执行 finally 中的代码,再抛出此异常,因为如果先抛出了异常,那 finally 代码块的内容就无法执行了。

finally 简直太牛了,即使它前面有 return 语句,也会等到 fianlly 代码块的语句执行完了后再返回,但是如果 finally 有 return 语句,就会覆盖之前的语句,可以利用 return 返回栈的概念分析。实例代码如下:

public static int f() {
        try {
            System.out.println("try"+ 5/0);
            return 1;
        }catch(Exception e){
            return 3;
        }finally {
            return 2;
        }
    }//此方法返回 2 

如果 catch 捕获了相应异常,但是在处理异常的过程中又发生了异常,那么此时本应该抛出异常,但是会先执行 finally 代码块的内容后再抛出异常。但是如果 finally 代码块中有 return 语句,不仅会覆盖之前所有的 return 语句,还会是的程序就此结束。之前未来得及处理的异常就这样被隐藏了。

finally 代码块中不要出现 return 语句,不要乱写。上面讲到的内容只是有可能会遇到这样的面试题,只要知道 finally 中的 return 语句有一个 return 返回栈的概念就可以了,利用这个概念辅助分析。

自定义异常类

为什么要使用自定义异常类?Java 异常机制就是设计来处理程序中不正确问题的一种手段。这些都属于程序运行上的问题,计算机是用来解决问题的,在现实中有很多功能性错误,也就是程序运行上没有问题,但是在具体业务功能上不符合需求。比如银行系统中如果用户取钱大于账户余额,这就属于业务功能性错误。这类问题可以巧妙的利用异常机制来完成对她的处理。

如何自定义异常?自定义异常首先要做的当然是继承 java.lang.Exception 类,不然你要自己重新写一个吗?然后提供一个空构造函数和一个有参的构造函数,在其内部调用 super 关键字给父类的私有属性 detailMessage 属性赋值。重写 toString() 方法,输出格式为:类名:detailMessage。根据具体需求还可以重写printStackTrace()方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,699评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,124评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,127评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,342评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,356评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,057评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,654评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,572评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,205评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,343评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,015评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,704评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,196评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,690评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,348评论 2 358

推荐阅读更多精彩内容

  • 引言 在程序运行过程中(注意是运行阶段,程序可以通过编译),如果JVM检测出一个不可能执行的操作,就会出现运行时错...
    Steven1997阅读 2,435评论 1 6
  • packagetestexcrpltiom; importjava.text.ParseException; im...
    猿学阅读 1,467评论 0 2
  • 这一日,我们几经辗转,来到贵州最有名的古镇故乡——镇远。 镇远是座历史悠久的苗乡古镇,苗族、侗族自治州...
    淡定姐姐阅读 276评论 0 1
  • 现在的男女认识真的 好简单,陌陌?探探?附近的人?等等各种交友平台,往往这样的感情真的不会有人去珍惜吧毕竟来的太快...
    过呀过呀阅读 274评论 0 0
  • 我知道这是今年最后一天和你在一起 你才哭的这么伤心 哭了一整天 整个江南因为你要走都哭了 秋天 我的情人 没有人知...
    王秋煜阅读 270评论 6 5