Java受检异常的一些思考

什么是异常?

要了解受检异常,首先要了解什么是异常。

在Java中,异常是一套能够一致地处理错误和恢复代码运行正常的机制。

在C语言中,他没有异常处理机制。如果一个函数出现了异常情况,例如一个除法的函数,被除数输入了0,这个时候需要把这个异常告诉函数调用者,一般情况下我们会通过返回一个特殊的值来告诉调用者出现异常,如返回-1。这是c语言常规的处理异常手法。这会引发另一个问题:我们需要在每次调用函数的时候,都进行条件判断,看返回值是否出现了异常,这样会让我们的代码出现了非常多的if判断语句。《Java编程思想》一书中讲到:每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常

不仅代码不美观和写很多的代码,而且容易漏掉一些情况没有进行判断,没有安全感。

这个是否需要有一套完整的异常处理机制,当出现问题的时候,把异常交给机制去处理,这样我们就可以把处理异常的代码和正常的代码分开,且不担心出现“漏网之鱼”。因而我们需要:

一套统一的错误报告机制,可以减少代码量、代码简洁的同时,能够把错误进行统一处理,不会有漏网之鱼。

关于这个问题在《Java编程思想》中是这样说的:异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。Java异常机制比常规的错误报告机制还做到了:

我们可以在一个地方集中处理错误,把正常的代码处理逻辑和错误情况处理逻辑分离,使用代码的阅读、编写和调试更加井井有条。

编译时还是运行时?

熟悉Java开发的读者知道Java异常机制拥有两种大类型的异常:运行时异常和编译时异常。

编译时异常是在编译的时候就能被检查出来的异常,也被称为受检异常(Checked Exception)。如IOException。运行时异常就是在运行的时候才会被检查的异常,如空指针异常。

而我们今天要讨论的就是关于运行时异常。这两种异常的本质差别是:检查的时机。一般情况来说,错误能在编译阶段就被检查出来是最好的,编译器帮我们解决错误。然而情况也不一定都是这样,因为处理异常需要我们写出更多的代码。如果每次调用一个方法都需要处理所有的异常,那么我们需要的工作量就太大了。例如空指针,我们知道几乎每个方法都会抛出这个异常,但是java并没有强制我们去捕获他。所以异常处理时机的分类是一个权衡的结果,既想让编译器尽多地帮我们检查错误,又不能让开发者代码编写太多额外的代码,需要在这两者之间找到一个最平衡的点。

“受检异常”究竟可不可取?

这个问题其实争论的历史非常久,新型语言Kotlin中就把受检异常去掉了。这样我们就没必要再去显示捕获任何异常。这在我第一次接触kotlin的时候感觉简直是一个噩梦:

因为我不知道调用的方法是否会抛出异常、抛出什么异常,那么我可能会忽略一些关键的异常没有捕获而导致代码的健壮性降低。

这也确实也是很多支持Java受检异常开发者的心声。有了受检异常,那么编译器会提醒我们我们调用的这个方法会抛出什么异常,我们需要去使用try-catch来捕获他,不会遗漏任何重要的异常。但事实真的如此吗?

受检异常真的能够避免所有的异常吗?不是的,我们依旧会出现空指针异常、非法状态异常等,我们并不会去显示捕获这一类的异常,他在java中称为运行时异常。那有读者可能会说:受检异常是可以让我们避免一些方法显示抛出的异常,并不是要处理所有的错误。因而,我们必须呀承认一个事实:无论是否有受检异常,均不能解决所有的异常,两者的差别是编译阶段处理异常的数量。而这两者的边界是一种权衡的结果使用受检异常,我们真的处理异常了吗?

CLU 的设计师们认为:我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序,是不现实的,只有在你知道如何处理的情况下才捕获异常。

Stroustrup 这样认为:这样一来几乎所有的函数都得提供异常说明了,也就都得重新编译,而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束,他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感

我们会发现现在的一种情况是:开发者捕获异常之后,并没有做具体的处理,而是对异常进行打印,或者干脆不做任何处理,这是不符合异常的规则的。只有知道如何处理异常的情况下才去捕获异常。如果捕获异常之后却不做任何处理,那么实际上是把异常“吃了”。因为如果异常发生了,我们以为我们处理了,但事实上我们并没有做任何处理,那么这个错误就会一直存在,反而更不利于我们对代码健壮性的维护。有读者可能会觉得,我们对于不知如何处理的异常可以往上抛,这会导致另一个问题:顶层接口的调用者会拿到非常多的异常类型而不知如何处理,最终只能向上抛给控制台,从而使得整套异常机制完全失效。

我们知道异常机制的目的之一就是为了减少代码,不用每次调用一个方法都去进行条件判断,而通过使用try-catch就可以把错误进行集中处理,把正常代码的逻辑和处理异常的逻辑分离开来,但是,受检异常的存在,当我们即使调用一个Thread.sleep()方法都必须进行try-catch,又如IO流的开启与关闭也都必须进行try-catch,,这样的代码量其实又回到原地了,并没有达到最初的目标:减少代码量。

《Java编程思想》一书认为:不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型;不在于什么时候进行检查,而是一定要有类型检查。也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那倒没关系。

即异常机制的重点在于一致的使用报告错误的模式和强制程序使用正确的类型。所以异常的存在,并不是要我们在编译时把所有异常都处理了,而是我们可以统一处理异常且让程序使用正确的类型。

我的观点

上面讲到我第一次接触到kotlin的时候非常反感他把checkedException去掉了,这让我非常没有安全感,我的原因是:调用别人的接口的时候,这时候没有编译器的提醒我会遗漏关键的异常而导致代码的健壮性下降。但,正如上述,如果不知道如何处理异常而去捕获异常,然后不做任何处理,这是在欺骗自己,获得一种虚假的安全感。我们并没有解决异常,而是把异常“吃了”,看起来程序并没有崩溃,但事实上我们只是把异常藏起来了,没有做任何处理。这种情况不如把异常往上抛到最顶层交给控制台处理,我们知道程序崩溃了,那么我们就知道得去解决这种异常,这样才能真正促进代码的健壮性。

而向上抛会会导致另一个问题:顶层接口的调用者会拿到非常多的异常而不知道如何处理,再次向上抛,最终导致整个异常机制失效。而如果每一层都只处理自己感兴趣的异常,剩下的异常就会自动抛到最顶层,代码会更加简洁,我们也可以更加专注我们要处理的异常。

这两个是我觉得“受检异常”的硬伤,。而其他,如频繁try-catch导致代码量增多,两种类型的代码导致逻辑不连贯等,相对来说这属于软伤,在面对“可以在编译阶段解决更多的异常并没有更强的说服力。

去掉受检异常对于java工程师来说可能会很没有安全感,因为:会存在一些异常没有处理,他们就像一些炸弹,但不知道他们什么时候爆炸。这确实是个问题,没有受检异常会有更多的异常留到运行时。但这个问题影响的效果可能并没有我们想象中的那么大。我们真正处理受检异常机制提醒的异常其实并不多,正如我上面所讲,如果我们捕获异常之后只是“吃掉它”,那么还不如抛给控制台。

那么“受检异常”是不是就一无是处?我觉得不是的。受检异常的目的是在编译阶段处理异常,去掉确实可以避免上述的两个问题,且让代码更加的简洁。但是换之的是需要给测试人员更多的压力和开发者自身的对代码健壮性的处理,需要有更多的精力去解决没有“受检异常”带来的问题。不是有“全局捕获异常吗”?频繁的崩溃和维护,并不是一个好的解决方案,如果能在编码阶段就把隐藏的问题解决是最好不过的。

从我自己的经历来说,我使用java和kotlin语言均开发过不同的项目,而没有受检异常并没有给我带来多大的障碍,反而不用使用try-catch让我在编写代码的时候更加舒适。但因我项目较小,所以可能并不能作为一个示例来证明,仅作为一个参考。而kotlin在这方面有一个我认为不好的特点:没有异常声明。即如果我想要看我调用的方法究竟会抛出什么异常时,需要一行一行代码去查看,而无法通过异常声明来得知。

所以,需要怎么做?我认为这两种解决方案各有千秋,看开发者如何取舍。而我个人觉得这是这种异常机制的限制,我们需要的是对异常机制的改进,如Java有了进行注解对异常进行忽略,这是一种进步。对这个问题的讨论正如对洋葱好不好吃的讨论是差不多的,不同的团队不同的需求取舍,会有不同的结果。讨论这个问题更重要的是了解不同的解决方案有不同的优缺点,我们在开发的时候,可以发挥不同处理方案的优势,注意不要踩坑。

没有最好的语言,只有更好的开发者。如果我们能够在每层都解决自己应该负责解决的异常,那么顶层接口就不会有高达八十多种异常的出现。如果对每种异常捕获之后都能够做好对应的处理,而不是把异常“吃掉”来获得虚假的安全感,那么受检异常也没有那么多的问题。如果我们可以更加规范地开发,那么受检异常,事实上问题也不会那么多。简单粗暴去除掉受检异常,可以通过迎合大部分开发者的惰性和不规范来解决受检异常存在的问题,但是这并不是最好的解决方法。所以我们能做的,不是去捍卫不同的立场,而是不断地提升自己的代码规范,通过代码规范来增强代码的健壮性。

以上就是Java受检异常的一些思考的详细内容,更多关于Java受检异常的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java并发编程示例(八):处理线程的非受检异常

    Java语言中,把异常分为两类: 受检异常: 这类异常必须在throws子句中被显式抛出或者在方法内被捕获.例如,IOException异常或ClassNotFoundException异常. 非受检异常: 这类异常不需要显式抛出或捕获.例如,NumberFormatException异常. 当一个受检异常在Thread对象的run()方法中被抛出时,我们必须捕获并处理它,因为run()方法不能抛出异常.而一个非受检异常在Thread对象的run()方法中被抛出时,默认的行为是在控制台打印出堆栈

  • Java如何实现自定义异常类

    这篇文章主要介绍了Java如何实现自定义异常类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 自定义异常类步骤 创建一个类继承异常父类Exception 在具体的实现方法首部抛出异常类(自己创建的那个类),throws的运用 在具体的实现方法的内部抛出异常信息,throw的运用 创建一个类继承异常父类Exception public class EmailException extends Exception { EmailException(

  • 一篇文章解决Java异常处理

    前言 与异常相关的内容其实很早就想写了,但由于各种原因(懒)拖到了现在.在大二开学前夜(今天是8.31)完成这篇博客,也算完成了暑期生活的一个小心愿. 以下内容大多总结自<Java核心技术 卷Ⅰ>,同时也加上了一些华东师范大学陈良育老师在<Java核心技术>Mooc中所讲的内容. 一.引例 假定你希望完成一个read方法,它的作用是读取一个文件中的内容并进行相关处理,如果你从未学过处理异常的方法,你可能会这样写: public void read(String filename)

  • java.lang.ExceptionInInitializerError异常的解决方法

    今天在开发的过程中,遇到java.lang.ExceptionInInitializerError异常,百度查了一下,顺便学习学习,做个笔记 静态初始化程序中发生意外异常的信号,抛出ExceptionInInitializerError表明在计算静态初始值或静态变量的初始值期间发生异常. 要理解这个异常从Java类中的静态变量初始化过程说起,在Java类中静态变量的初始化顺序和静态变量的声明顺序是一致的.示例程序为: package com.lang.ininitialException; im

  • 浅析Java中的异常处理机制

    异常处理机制 1.抛出异常 2.捕获异常 3.异常处理五个关键字: try.catch.finally.throw.throws 注意:假设要捕获多个异常:需要按照层级关系(异常体系结构) 从小到大! package exception; /** * Java 捕获和抛出异常: * 异常处理机制 * 1.抛出异常 * 2.捕获异常 * 3.异常处理五个关键字 * try.catch.finally.throw.throws * 注意:假设要捕获多个异常:需要按照层级关系(异常体系结构) 从小到大

  • 带你了解Java中的异常处理(下)

    今天继续讲解java中的异常处理机制,主要介绍Exception家族的主要成员,自定义异常,以及异常处理的正确姿势. Exception家族 一图胜千言,先来看一张图. Exception这是一个父类,它有两个儿子,IOException和RuntimeException,每个儿子都很能生,所以它有着一堆的孙子,但其实,Exception家族还有一个大家伙,那就是Throwable,这是一个接口,看名字就知道意思,就是"可被抛出"嘛,它还有一个同父异母的哥哥,那就是Error,这家伙可

  • 带你了解Java中的异常处理(上)

    当当当当当当,各位看官,好久不见,甚是想念. 今天我们来聊聊Java里的一个小妖精,那就是异常. 什么是异常?什么是异常处理? 异常嘛,顾名思义就是不正常,(逃),是Java程序运行时,发生的预料之外的事情,它阻止了程序按照程序员的预期正常执行. 异常处理,应该说异常处理机制,就是专门用来制服这个小妖精的法宝.Java中的异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰. 简而言之,Java异常处理就是能让

  • JAVA异常处理机制之throws/throw使用情况的区别

    JAVA中throw和throws的区别:https://www.cnblogs.com/xiohao/p/3547443.html 区别:(摘自上面的博客) 1.throws出现在方法函数头:而throw出现在函数体. 2.throws表示出现异常的一种可能性,并不一定会发生这些异常:throw则是抛出了异常,执行throw则一定抛出了某种异常. 3.两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用

  • 解决java.util.NoSuchElementException异常的问题

    java.util.NoSuchElementException 报错的行数是一个scnner的next,本来和老师讨论了半天没有什么头绪,错误的原因是,因为找不到下一个元素,然后,如果把上一个函数中操作system.in的函数注释掉,就不会出现问题. 后来,老师一问,就是因为在上面函数的时候,我将system手动关闭掉了,系统资源不同于文件,一旦关闭就不能再打开,这就是问题的原因. 系统资源一旦释放就不能再开启了,所以只有确定不在使用系统的时候,才能将流关闭. 补充知识:对于springboo

  • Java受检异常的一些思考

    什么是异常? 要了解受检异常,首先要了解什么是异常. 在Java中,异常是一套能够一致地处理错误和恢复代码运行正常的机制. 在C语言中,他没有异常处理机制.如果一个函数出现了异常情况,例如一个除法的函数,被除数输入了0,这个时候需要把这个异常告诉函数调用者,一般情况下我们会通过返回一个特殊的值来告诉调用者出现异常,如返回-1.这是c语言常规的处理异常手法.这会引发另一个问题:我们需要在每次调用函数的时候,都进行条件判断,看返回值是否出现了异常,这样会让我们的代码出现了非常多的if判断语句.<Ja

  • 浅谈java中异常抛出后代码是否会继续执行

    问题 今天遇到一个问题,在下面的代码中,当抛出运行时异常后,后面的代码还会执行吗,是否需要在异常后面加上return语句呢? public void add(int index, E element){ if(size >= elements.length) { throw new RuntimeException("顺序表已满,无法添加"); //return; //需要吗? } .... } 为了回答这个问题,我编写了几段代码测试了一下,结果如下: //代码1 public

  • java 使用异常的好处总结

    java 使用异常的好处总结 一.分析 Java异常处理机制确实比较慢,这个"比较慢"是相对于诸如String.Integer等对象来说,单单从对象的创建上来说,new一个IOException会比String慢5倍,这从异常的处理机制上也可以解释:因为它执行fillStackTrace方法,要记录当前栈的快照,而String类则是直接申请创建一个内存创建对象,异常类慢一筹也在所难免.  二.场景 我们知道异常是主逻辑的例外逻辑,举个例子来说,比如我们能在马路上走(这时主逻辑),突然开

  • java的异常与处理机制分析【附面试题】

    本文实例讲述了java的异常与处理机制.分享给大家供大家参考,具体如下: java的异常机制 Throwable类 Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别.JDK中内建了一些常用的异常类,我们也可以自定义异常. Throwable又派生出Error类和Exception类. 错误:Error类以及他的子类的实例,代表了JVM本身的错误.错误不能被程序员通过代码处理,Error很少出

  • Java常见异常及处理方式总结

    一.概述 异常指不期而至的各种状况,它在程序运行的过程中发生.作为开发者,我们都希望自己写的代码 永远都不会出现 bug,然而现实告诉我们并没有这样的情景.如果用户在程序的使用过程中因为一些原因造成他的数据丢失,这个用户就可能不会再使用该程序了.所以,对于程序的错误以及外部环境能够对用户造成的影响,我们应当及时报告并且以适当的方式来处理这个错误. 之所以要处理异常,也是为了增强程序的鲁棒性. 异常都是从 Throwable 类派生出来的,而 Throwable 类是直接从 Object 类继承而

  • Java 常见异常(Runtime Exception )详细介绍并总结

    本文重在Java中异常机制的一些概念.写本文的目的在于方便我很长时间后若是忘了这些东西可以通过这篇文章迅速回忆起来. 1. 异常机制 1.1 异常机制是指当程序出现错误后,程序如何处理.具体来说,异常机制提供了程序退出的安全通道.当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器. 1.2 传统的处理异常的办法是,函数返回一个特殊的结果来表示出现异常(通常这个特殊结果是大家约定俗称的),调用该函数的程序负责检查并分析函数返回的结果.这样做有如下的弊端:例如函数返回-1代表出现异常

  • java 解决异常 2 字节的 UTF-8 序列的字节2 无效的问题

    java 解决异常 2 字节的 UTF-8 序列的字节 2 无效的问题 最近做项目,遇到异常 2 字节的 UTF-8 序列的字节 2 无效的问题,上网找了下资料,这里记录下解决方法,有遇到同样问题的大家,可以看下 详细异常: 十二月 08, 2015 7:16:55 下午 org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() for servlet [jsp] in context with path

  • Java编程异常简单代码示例

    练习1 写一个方法void triangle(int a,int b,int c),判断三个参数是否能构成一个三角形.如果不能则抛出异常IllegalArgumentException,显示异常信息:a,b,c "不能构成三角形":如果可以构成则显示三角形三个边长.在主方法中得到命令行输入的三个整数,调用此方法,并捕获异常. 两边之和大于第三边:a+b>c 两边之差小于第三边:c-a package 异常; import java.util.Arrays; import java

  • Java处理异常2种机制关键字区别解析

    这篇文章主要介绍了java处理异常2种机制关键字区别解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在异常处理的过程中,throws和throw的区别是? throws:是在方法上对一个方法进行声明,而不进行处理,而是向上传,谁调用谁处理. throw:是在具体的抛出一个异常类型. throws的栗子: throws的话,就是这个方法有可能会产生异常,而我只是将它声明出去,我自己不处理,如果有人调用的时候,可以知道,这个方法,有可能会抛出异

随机推荐