Java进阶——详谈Exception

写在最前

最近笔者在撰写JavaWeb与自动化相结合的教程,上篇入口在这里,第二篇还在创作中,在发布之前,让我们先来讨论一个Java的重要技能,Exception。

实现程序的运行是所有初级的程序员所追求的,Thinking in Java 因此成为了很适合入门的一本书,然而随着代码行数的累积,越来越多的坑也随之到来。此时,对基础知识更深层次的理解就尤为关键。在JavaWeb与自动化结合的应用中,无脑抛出异常会导致代码的冗余与羸弱,今天发的这篇文章将仔细地对Exception的运用进行分析。

需要注意的是,本篇文章并不是对如何抛出异常的基础进行讲解,需要读者对Exception机制有一定了解,文中部分用例来自Effective Java,在这里同时向读者推荐这本书作为Java进阶的重要工具,文末附录中有笔者Exception部分的英文笔记供大家参考。

使用Exception的情景

不要在类似迭代的循环中使用Exception,尤其是涉及ArrayIndexOutOfBounds,如下所示:

try {
    int i = 0;
    while(true)
        array[i++].doSomething();
} catch(ArrayIndexOUtOfBoundsException e) {

}

主要因为此时使用try-catch有三点显而易见的坏处:

  • 这样做违背于JVM设置exception处理的原则,JVM会花费更多的时间来处理。
  • 把Code放在try-catch语句中使得一些JVM运行中的优化被封禁。
  • 规范的迭代写法是经过优化的,通过JVM的内部处理,避免了很多赘余的检查机制,是更合适的选择。

如果我们在try-catch语句中调用了另一个数组,这个数组中出现了ArrayIndexOutOfBounds的异常,其中的bug就会被catch exception所蒙蔽。相反,标准的迭代写法会及时的终止线程的执行,报出错误并且给出追踪错误的路径让程序员更轻松地定位bug的来源。

现在我们通过Java的Iterator接口来看一下标准迭代写法,在标准的迭代写法中,我们利用hasNext()作为state-testing判断方法,来实现state-dependent方法next(),代码如下:

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
    //...
}

综上所述,Exception是为了异常或者说例外的情况而准备的,不应该在普通的语句中使用,并且程序员也不该写出强迫他人在正常流程的语句中使用Exception的API。

Checked与Unchecked的区别

在Java中Throwable是Exception与Error的父类,而在Effective Java书中,Throwable被分为了以下三类:

  1. Checked Exceptions
  2. Runtime Exceptions
  3. Errors

其中,2和3都是Unchecked Throwable,所以在我们分析Java的异常类时,从Checked与Unchecked两个逻辑角度来分析会更加清晰。

Checked Exception指那些在编译过程中会检查的,这类错误在运行中是“可恢复的”。我们需要在写程序时将其抛出,换而言之,这些异常应该并不是由程序员所导致,而是类似”例行检查“。

相反,Runtime Exception指的就是程序员本身制造出来的错误,在文章的第一部分中我们已经明确指出,此类错误不应该被抛出,而应该由程序员自己去修复。需要注意的是,一般来说,我们自己设计的Exception应该作为Runtime Exception的直接或者间接子类。如果你对Exception理解得比较浅,暴力地把Runtime Exception的子类背下来,对debug的帮助也相当大,可以快速定位代码中的问题。

Errors与Exception不同,他是与JVM相关的,当你在写算法时看到栈溢出,那并不是你对语言的理解导致你的代码出现漏洞,而是你的数据结构使得JVM出现resource deficiency或invariant failures使得程序无法继续执行,所以看到Errors的时候,我们也不应将其抛出,而是应该对代码结构进行修改处理。

综上所述,如果在运行中可恢复,那么我们就应该将这种Checked Exception抛出。当不清楚该如何做的时候,抛出Runtime Exception。重要的是,不要定义既不是Checked Exception子类也不是Runtime Exception子类的Throwable,并且记得在你自定义的Checked Exception中加入方法使代码能在运行中恢复。

Checked Exception的使用技巧

我们经常会遇到这种问题,在一个方法中,有一行代码需要抛出Exception,我们需要将他包裹在try-catch语句中。在Java8之后,我们在使用此API时必须抛出这个异常,这极大地降低了我们代码的质量。

解决这个问题最简单的方法可能就是我们在运行此方法是不返回任何值,但是如果这样做我们就少了很多通过此方法返回信息和数据的机会。

因此我们提供了另一种解决方式,那便是通过将需要抛出Checked Exception的方法拆为两个方法,使其转变为一个Unchecked Exception。第一个方法通过返回一个boolean值来指明此Exception是否应该被抛出,第二个再进行剩余的操作。下面是一个转变的简单例子。

包裹在try-catch中的语句:

try {
    ted.read(book);
} catch (CheckedException e) {
    //...do sth.
}

下面是改造后的代码:

if (ted.understand(book)) {
    ted.read(book);
} else {
    //...do sth.
}

简单来说,就是本来是再Ted”读“这个方法中抛出他看不懂这个书的异常,但我们将其拆分为”是否理解“与“读”两个方法对其进行重构,来避免try-catch的运用。

总的来说,重构Checked Exception是为了代码更简洁更可靠,避免了对Checked Exception的过度使用,因为过度使用会导致API对使用者很不友好。在遇到上面所说的情况时,首先考虑能否使用返回值为空的方法,因为这是最直接最简单的解决方式。

优先使用标准库中的Exception

使用Java库中提供地Exception有三大好处:

  1. 使你的API更容易地被学习与使用,因为大多数程序员都了解标准的异常
  2. 让使用了你的API的程序阅读起来更轻松
  3. 更少地占用内存并且更快地对Class进行加载(JVM)

不要直接重用Exception, RuntimeException, Throwable或是Error这些父类,常用的Exception在下表中列出。

Exception 使用场景
IllegalArgumentException 不匹配的非空参数的传递
IllegalStateException 未初始化的对象(对象状态不匹配)
NullPointerException 在未预期的情况下遭遇空指针
IndexOutOfBoundsException 索引参数超出范围
ConcurrentModificationException 多线程对同一个对象进行修改
UnsupportedOperationException 此对象不支持对此方法的引用

需要注意的是,重用的Exception一定要与记录的语义一致,在文档中详细说明,并不只是简单地匹配Exception的名字。

结语

除了上面详述的几点外,还要注意的是,首先,每个方法抛出的异常都要有文档。其次,保持异常的原子性。最重要的是,千万不要在catch中忽略掉捕获到的异常

关于异常处理对于很多人来说只是Alt+Enter,但是在代码优化阶段经常很让人头疼,希望本文能使大家有所启发,对于接下来教程中的一些代码有更好的理解,也欢迎大家提问,共同提高。

附录:Effective Java 读书笔记

Chapter 10 EXCEPTIONS

Item 69: Use exceptions only for exceptional conditions

Do not use try catch to handle your loop, it might mask the bug and is also very slow.

Exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow and do not write APIs that force others to do so.

A well designed API must not force its clients to use exceptions for ordinary control flow.

In iteration codes, one should use hasNext() to decide the life circle of a loop.

Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

Use checked exceptions for conditions from which the caller can reasonably be expected to recover.

Use runtime exceptions to indicate programming errors.

All of the unchecked throwables you implement should subclass RuntimeException (directly or indirectly).

Don't define any throwables that are neither checked exceptions nor runtime exceptions.

Provide methods on your checked exceptions to aid in recovery.

Item 71: Avoid unnecessary use of checked exceptions

In Java 8, methods throwing checked exceptions can't be used directly in streams.

How to solve the problem that if a method throws a single checked exception, this exception is the sole reason the method must appear in a try block and can't be used directly in streams?

The easiest way to eliminate this is to return an optional of the desired result type.

You can also turn a checked exception into an unchecked exception by breaking the method that throws the exception into two methods, the first of which returns a boolean indicating whether the exception would be thrown.

Item 72: Favor the use of standard exceptions

The Java libraries provide a set of exceptions that covers most of the exceptions-throwing needs of most APIs.

Benefits: makes your API easier to learn because it matches the established conventions, makes programs using your API easier to read, a smaller memory footprint and less time spent loading classes.

Do not reuse Exception, RuntimeException, Throwable, or Error directly.

Reuse must be based on documented semantics, not just on name.

Item 73: Throw exceptions appropriate to the abstraction

Higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction, aka. Exception Translation.

While exception translation is superior to mindless propagation of exceptions from lower layers, it should not be overused.

If it is not feasible to prevent or to handle exceptions from lower layers, use exception translation, unless the lower-level method happens to guarantee that all of its exceptions are appropriate to the higher level.

Item 74: Document all exceptions thrown by each method

Always declare checked exceptions individually, and dovument precisely the conditions under which each one is thrown using @throws tag.

Use the Javadoc @throws tag to document each exception that a method can throw, but do not use the throws keyword on unchecked exceptions.

If an exception is thrown by many methods in a class for the same reason, you can document the exception in the class's documentation comment.

Item 75: Include failure-capture information in detail messages

To capture a failure, the detail message of an exception should contain the values of all parameters and fields that contributed to the exception.

Do not include passwords, encryption keys, and the like in detail messages.

Item 76: Strive for failure atomicity

A failed method invocation should leave the object in the state that it was in prior to the invocation.

Item 77: Don't ignore exceptions

An empty catch block defeats the purpose of exceptions.

If you choose to ignore an exception, the catch block should contain a comment explaining why it is appropriate to do so, and the variable should be named ignored.

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

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,266评论 0 10
  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,684评论 0 3
  • 晨时, 犹欲还雨, 单坐里,问时雨下。 三两滴秋水穿窗落地,几处生花。 步入中庭, 怎知他,乍还暖, 只得观雨...
    华尊依月阅读 306评论 2 10
  • 俄罗斯花样滑冰选手米哈伊尔·科尔亚达接受采访表示,不用因为节目中的错误而感到失望,负分也是分数。 “我明白我们在错...
    云游四方的旅人阅读 450评论 0 0
  • 今天帮丫头听写生字,我看她写的字还可以。可她看不过眼,都用橡皮擦了重新写了一遍。要求真严格啊。孩子,为你的态度点赞。
    91b6f8355762阅读 123评论 0 1