Java编程思想笔记12.异常处理

一年又一年,字节跳动 Lark(飞书) 研发团队又双叒叕开始招新生啦!
【内推码】:GTPUVBA
【内推链接】:https://job.toutiao.com/s/JRupWVj
【招生对象】:20年9月后~21年8月前 毕业的同学
【报名时间】:6.16-7.16(提前批简历投递只有一个月抓住机会哦!)
【画重点】:提前批和正式秋招不矛盾!面试成功,提前锁定Offer;若有失利,额外获得一次面试机会,正式秋招开启后还可再次投递。

点击进入我的博客

Java异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更自信:你的应用中没有未处理的错误。

12.1 概念

异常机制使代码的阅读、编写和调试工作更加井井有条。

12.2 基本异常

异常情形:是指组织当前方法或作用域继续执行的问题。
抛出异常:异常情形发生时,程序在当前环境无法获得必要的信息来解决问题,不能继续执行,这是只能从当前环境跳出,把问题提交给上一级环境。

抛出异常流程
  1. 在堆上new一个异常对象
  2. 当前执行路径种植,并且从当前环境中弹出对异常对象的引用
  3. 异常处理机制接管程序,并开始寻找一个恰当的地方(异常处理程序)来执行程序。
异常的作用
  1. 异常是我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线
  2. 异常可以看作是一种内建的恢复系统,当程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点。
  3. 异常最重要的方面就是如果发生问题,不允许程序沿着其正常的路径继续走下去。

12.2.1 异常参数

所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串(错误信息)作为参数。

12.3 捕获异常

监控区域:一段可能产生异常的代码,并且后面跟着处理这些异常的代码。

12.3.1 try块

把所有可能产生异常的动作放到try块中,然后在一个地方就可以捕获所有异常。

12.3.2 异常处理程序

抛出的异常必须在某处得到处理,这个地点就是异常处理程序,以紧跟在try块之后的catch块表示。
catch块可以有多个,当异常被抛出时,异常处理程序只会处理第一个匹配的抛出异常,然后不会再执行剩下的语句。

12.3.3 终止与恢复

异常处理理论上有两种基本模型:
  • Java支持终止模型,这这种模型中,假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。
  • 另一种成为恢复模型,意思是异常处理程序的工作是执行错误,然后重新尝试调用出问题的方法,并认为第二次可以成功。回复模型不实用的主要原因是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。

12.4 创建自定义异常

Java提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类。创建自定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承。

System.err与System.out

通过System.err可以将错误发送给标准错误流,这通常比把信息输出到System.out要好,因为System.out也许会被重定向,而System.err不会。e.printStackTrace()也是把信息发送给System.err

12.4.1 异常与记录日志

class LoggingException extends Exception {
    private static final Logger LOGGER = Logger.getLogger("LoggingException");

    public LoggingException() {
//        StringWriter writer = new StringWriter();
//        printStackTrace(new PrintWriter(writer));
        LOGGER.severe(this.toString());
    }
}

如上所示:可以把异常的信息打印到日志java.util.logging中,默认的日志输出是System.err,也可以配置为文件等。

public class Test {
    private static final Logger LOGGER = Logger.getLogger("Test");

    static void logException(Exception e) {
//        StringWriter writer = new StringWriter();
//        printStackTrace(new PrintWriter(writer));
        LOGGER.severe(e.toString());
    }

    public static void main(String[] args) {
        try {
            throw new RuntimeException();
        } catch (Exception e) {
            logException(e);
        }
    }
}

如上所示:一般来说,在自定义的异常类(以及其他人的异常类)中不会耦合日志系统的信息,我们需要捕获异常然后输出异常信息到日志系统,所以需要在异常处理程序中产生日志消息。
一般来说,异常最重要的信息就是抛出的异常类本身,其他的功能基本上不用去管。

12.5 异常说明

可以在方法上用throws关键字主动声明该方法会抛出哪些异常,来告诉调用此方法的程序员去处理这些异常。这种在编译时被强制检查的异常称为被检查的异常

void f() throws Exception {
}
注意:
  1. 即使没有throws并不表示此方法不会抛出异常。
  2. 如果方法中产生了异常却没有处理,编译器会强制你要么处理这个异常,要么就主动声明抛出这种异常
  3. 可以声明抛出异常,实际上却不抛出。这样的好处是为异常先占个位子,在定义抽象类和接口的时候尤为重要,这样派生类或接口就能抛出这些预先声明的异常。

12.6 捕获所有异常

因为Exception是所有异常的基类,所以通过catch(Exception e)可以捕获所有异常。
尽量捕获子类的异常,这样可以携带更加细节的信息,最好把catch(Exception e)放在处理程序的末尾,防止它在其他处理程序之前先把异常捕获了。

12.6.1 栈轨迹

  • printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法返回由栈轨迹中的所有元素构成的数组,其中每一个元素都表示栈中的一帧。
  • 数组中第0个元素是栈顶元素,并且是调用序列中的最后一个方法调用,并且数组中元素下标按照调用过程逆序排列。
  • 数组中每个元素StackTraceElement,由类名、方法名、文件名、第几行组成。

12.6.2 重新抛出异常

  • 可以把捕获的异常在catch块中向上一级环境中抛出,此时同一个try块中其他catch块将会被忽略。
  • 调用e.fillInStackTrace()可以返回一个Throwable对象,它是通过把当前的调用栈信息填入原来的异常对象,此时该行将成为异常新的发生地,之前的异常在printStackTrace()方法中将不会打印(但没有丢失)。
  • 捕获原来的异常之后可以抛出另一个新的异常,效果类似与e.fillInStackTrace(),不同的是有关原来异常发生点的信息会被丢失,只剩下新的异常抛出点。

12.6.3 异常链

  • 捕获原来的异常之后可以抛出另一个新的异常,并且希望把原始异常的信息保存下来,这就是异常链
  • Throwable的子类在构造器中接受一个cause对象作为参数,这个cause就表示原始异常,此时就可以在抛出新异常的同时追踪到之前的异常。
  • 要注意的是,Throwable的子类并不一定有这个构造器,此时你可以用initCause()方法。
  • 通过e.getCause()获取原始异常。
public class Test {
    public static void main(String[] args) throws Exception {
        try {
            g();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void f() throws Exception {
        throw new IndexOutOfBoundsException();
    }

    private static void g() throws Exception {
        try {
            f();
        } catch (Exception e) {
            // throw new RuntimeException(e);
            RuntimeException ee = new RuntimeException();
            ee.initCause(e);
            throw ee;
        }
    }
}
// Output:
java.lang.RuntimeException: java.lang.IndexOutOfBoundsException
    at s2.Test.g(Test.java:26)
    at s2.Test.main(Test.java:11)
Caused by: java.lang.IndexOutOfBoundsException
    at s2.Test.f(Test.java:18)
    at s2.Test.g(Test.java:23)
    ... 1 more

12.7 Java标准异常

异常图.jpg

Throwable这个类被用来表示任何可以作为异常抛出的类。它分为两种类型:

  1. Error:表示编译时和系统错误,除特殊情况外,我们不需要理会此异常。
  2. Exception:可以被抛出的异常,在JAVA类库、用户方法及运行时故障中都可能抛出的异常,我们通常关心此异常。

12.7.1 RuntimeException

运行时异常(也称为不受检查异常)会被JVM自动抛出,所以不需要异常说明中把它们列出来。

只能在代码中忽略RuntimeException及其子类型的异常,其它类型的异常的处理都是由编译器强制执行的,RuntimeException代表的是编程错误:
  1. 无法预料的错误,比如从控制范围外传递来的null引用。
  2. 程序员应该在代码中检查及避免的错误。
  3. 在一个地方发生的异常,常常会在另一个地方发生错误。
不应该把异常处理机制当做是单一的用途的工具

虽然它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制范围外的不确定因素导致的,但是它对于发现某些编译器无法检测到的编程错误也是很有帮助的。

12.8 使用finally进行清理

无论异常是否被抛出,finally块中的语句总会被执行到。

12.8.1 finally用来做什么

对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块里发生了什么,内存总能得到释放
对Java来说,当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。如已经打开对文件或网络连接。

12.8.2 return与finally

  1. 不管有没有出现异常,finally块中代码都会执行;
  2. trycatch中有return时,finally仍然会执行;
  3. finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
  4. finally中最好不要包含return,否则程序会提前退出,返回值不是trycatch中保存的返回值。

12.8.3 缺憾:异常丢失

        try {
            throw new IllegalAccessException();
        } catch (Exception e) {
//            throw new IndexOutOfBoundsException();
            return;
        }

如果在finally块中重新抛出或直接return都会使原来的异常丢失。

12.9 异常的限制

  • 当覆盖方法的时候,只能抛出在基类方法的异常说明中列出的那些异常(此处异常指检查性异常)。因为如果子类抛出的异常>父类抛出的异常的话,在向上转型的时候,就父类方法并没有声明子类抛出的异常,这样就会忽略掉该异常。换句话说,在继承和覆盖的时候异常只能缩小不能扩大
  • 异常限制对构造器不起作用
  • 子类构造器不能捕获父类构造器的异常(因为调用父类构造器必须是第一行语句)。

12.10 构造器

  • 对于在构造阶段可能会抛出异常,并要求清理的类,最安全的做法是使用嵌套的try子句。
public class Test {
    public static void main(String[] args) {
        try {
            A a = new A();
            try {
                a.func();
            } finally {
                a.dispose();
            }
        } catch (Exception e) {
            System.out.println();
        }
    }
}

class A {
    public A() throws IOException {
    }
    
    public void func() {}
    
    // 清理该对象相关资源
    public void dispose() {}
}
  • 虽然嵌套的try子句是合法的,但是嵌套的try语句并不是一种很优雅的编码方式。Java7中新增了可以在try()自动关闭流的写法。
  • 在创建需要清理的对象之后,立即进入一个try-finally语句块。
  • finally块中依然有可能抛出异常,所以你可能需要额外的try-finally代码块。

12.11 异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也可以匹配其基类的处理程序。

12.12 其他可选方式

异常处理的一个重要原则:只有在你知道如何处理的情况下才捕获异常。
异常处理的一个重要目标:就是把错误处理的代码同错误发生的地点相分离。

“被检查的异常”使得问题变得有些复杂,因为你可能还没准备好处理错误的时候,就被迫加上了try-catch语句,这时如果吞掉异常,将会产生严重的问题。

12.12.1 历史:略

12.12.2 观点:略

12.12.3 把异常传递给控制台

最简单而不用写多少代码就能保护异常信息的方法,就是把它们传递给控制台(及日志文件等)。

12.12.4 把“被检查的异常”转换为“不检查的异常”

try{
    //…to do something useful
} catch(IDontKnowWahtToDoWithThisCheckException e){
    throw new RuntimeException(e);
}

把“被检查的异常”转换为“不检查的异常”:如果想把“被检査的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息 。
继续向上抛出异常:你可以不写try-catch子句或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”。

12.13 异常使用指南

  1. 在恰当的级別处理问题。(在知道该如何处理的情况下才捕获异常)
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修补,然后绕过异常发生的地方继续执行。
  4. 用别的数据进行计算,以代替方法预计会返回的值。
  5. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
  6. 把当前运行坏境下能做的事情尽量做完,然后把不同的异常抛到更高层。
  7. 终止程序。
  8. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛著也很烦人)
  9. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮做长期投资)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容