Java调用的目标发生了异常方法的时候怎样处理异常类型

  【IT168 评论】处理 Java 中的异常情况并不是个轻松的话题,什么样的异常需要如何处理,常让新手们觉得难以理解,甚至有经验的开发人员也可能花几个小时讨论不决。

  正因如此,大多数开发团队有着一套自己的规则,如果你是团队里的新人,你会对团队之间对于这个规则的巨大差别感到惊讶。

  即便是这样,也有一些大多数团队都使用的好做法。下面列出了 9 个比较重要的处理方式,可以帮你上手或者提升你的异常处理能力。

  在 try 块中使用资源时常发生,比如 InputStream ,是需要在用完之后关掉的。一个通常发生的错误是,在 try 块的末尾关掉资源:

  顺利的话,只要没有异常抛出,这种方法似乎可以工作得很好,try 中的所有语句都会被执行,然后资源也被关掉。

  不过你因为某个原因添加了 try ,其中调用的一个或多个方法可能就会引发异常,甚至有可能是你自己引发了异常,此时就无法运行到 try 的结尾,结果资源没法被关掉。

  因此,你应该把清理资源的代码都放在 finally 块中,或者使用 Try-With-Resource 语句。

  和 try 中的最后几行不一样,无论是 try 块被成功执行后还是在 catch 块中处理了异常,finally 块总是能被执行。这样,你可以确保清掉所有打开的资源。

  如果你使用的资源实现接口是 AutoCloseable,就可以用这个语句,多数Java 标准资源都这么做。当你在 try 中打开资源,它可以在 try 执行完毕后或异常处理完后自动关闭资源。

  2、更喜欢特定的异常

  你引发的异常越具体越好。时刻想着那些并不知道你代码的同伴,或者几个月之后的你,他们需要调用你的方法处理异常。

  所以,要尽可能地提供更多信息,确保你的 API 更容易理解。 这样,调用你的方法的人才能更好地处理异常,或者避免在检查上浪费多余的时间。

  3、把指定的异常记录下来

  无论在什么时候你在方法签名中指定了一个异常,你都应该在你的 Javadoc 中记录下来。这和上一个做法目的相同,都是给调用者尽量多的信息,便于他们避免或者处理异常。

  所以,要确保你在 Javadoc 中添加了 @throws 申明,说明什么样的情况会引发异常。

  4、用描述性消息引发异常

  这条和前两条背后的想法差不多,只是这次不用给方法调用者提供什么信息了。因为日志文件中或者监视器里收到异常报告时,每个人都必须读取异常消息。

  因此,应该尽可能准确地描述问题,并提供最相关的信息帮助了解异常事件。

  请不要误解我,我并不是让你写一大段文字。你应该在一两句话中为该异常作出解释,这能帮助你的运维团队了解问题的严重程度,同时也让你在分析服务异常时更轻松。

  如果你引发了一个特定的异常,它的类名就可能已经描述了错误类型,所以你也不用再提供更多信息了。NumberFormatException 是个比较好的例子,当你在给一个字符串提供了错的格式,类 java.lang.Long 的构造函数就会引发这个异常。

  NumberFormatException,这个名字已经告诉你问题所在了,你只需要提供导致问题的字符串。如果异常类的名称不具有表达性,需要在消息中提供所需的信息。

  5、优先抓住最具体的异常

  多数 IDE 可以帮助你实现这一条。 当你在尝试优先捕获较少特定的异常时,它们会报告一个无法访问的代码块。

  总是优先捕获最具体的异常类,并将低精确度的 catch 块添加到列表末尾。

  6、别去抓可抛出的对象

  可抛出(throwable)是所有异常和错误的超类,虽然你可以在 catch 语句中使用,但是你永远都不应该用它。

  如果你在 catch 语句中使用可抛出对象,结果不仅仅是抓住所有的异常,它还会抓出所有的错误。错误由 JVM 抛出,会指出那些不打算被应用程序处理的严重问题。这一类型的代表例子是 OutOfMemoryError,以及 StackOverflowError,两者都是由不在应用控制之内的情况引发的,无法处理。

  所以,除非你绝对确定处于异常情况下,并且你有能力或被要求去处理错误,否则不要去抓可抛出对象。

  你有没有分析过一个只有用例的第一部分被执行了的错误报告?

  这种情况的原因往往是一个被忽视的异常,那个开发者可能非常确定它不会再被抛出,还添加了一个并不能处理或者记录异常的 catch 块。然后,当你发现这个块时,你还很有可能找到一个名为“永远不会发生”的著名评论。

  你当然有可能在分析一个会发生不可能的问题。

  所以,不要忽视一个异常。你不知道代码将来会发生怎样的改变,可能有人会删除你用来防止异常事件的验证,全然不知这会导致问题。或者,抛出异常的代码被修改,导致在同一个类里抛出多个异常,而且调用代码也不能阻止所有的异常了。

  你至少应该写一个日志消息,告诉大家发生了难以想象的事情,需要有人做检查。

  8、别记录完又抛出

  这一条可能是这个列表中最常被忽视的。不过,你总是能找到很多代码段甚至是库里,有异常被捕获、记录然后又抛出。

  一般大家的直觉是,异常发生时记录它然后再把它抛出,这样可以方便调用者妥善处理该异常。可问题是往往大家会为同一个异常写入多个错误消息。

  而且后添加的消息没有任何其他信息。我们在 第 4 条里说过,异常消息应该描述异常事件。然后堆栈跟踪会告诉你抛出异常的是哪个类,那种方法和哪一行。

  如果你需要添加更多的信息,应该捕获异常并把它打包在一个自定义的信息中。 但请务必遵循第 9 条。

  所以,只有在你想处理该异常时再捕获它。否则就在方法签名中指明它,让调用者去处理。

  9、在不损耗异常的情况下打包它

  有时,抓住标准的异常并将其打包到自定义异常中更好。比较典型的是应用程序或框架特定的业务异常,你可以添加其他信息,还可以为异常类执行特殊处理。

  这样做的过程中,要确保把原始的异常设置为原因(异常类提供了一个特定的构造函数用来接受可抛出对象作为参数)。不这样做的话,原始异常的堆栈跟踪和消息就会丢失,导致异常的事件难以分析。

  如你所见,在你抛出或捕获异常的时候,应该考虑很多不同的事情,其中大多数都是为了提高代码的可读性或 API 的可用性。

  异常通常是由一个错误的处理机制和同时使用的沟通媒介导致的。因此,你应该和同事讨论你要用的做法和规则,让每个人都理解通用的概念,然后用相同的方式执行这些做法和规则。

如果你一眼就能看出答案,那么这篇文章你就不用浪费时间看啦。输出结果为:

      在没有异常机制的时候比如C语言,我们是这样处理的:通过函数的返回值来判断是否发生了异常(这个返回值通常是已经约定好了的),调用该函数的程序负责检查并且分析返回值。虽然可以解决异常问题,但是这样做存在几个缺陷:

  1. 容易混淆。如果约定返回值为-11111时表示出现异常,那么当程序最后的计算结果真的为-1111呢?
  2. 代码可读性差。将异常处理代码和程序代码混淆在一起将会降低代码的可读性。
  3. 由调用函数来分析异常,这要求程序员对库函数有很深的了解

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

      在java中,所有的异常都有一个共同的祖先,Throwable,它有两个重要的子类,Exception(异常)和Error(错误)。Error是指程序无法处理的错误,通常是指jvm运行时出现的问题,比如当jvm不再具有继续执行操作所需的内存资源时,将抛出OutOfMemory,当error发生时,jvm一般选择终止线程。

5: istore_0 //保存栈顶元素到局部变量表中索引为 0 的 slot 中,x 的值存在这里。 7: ireturn //返回栈顶元素,即 x = 1;正常情况下函数运行到这里就结束了,如果出现异常根据异常表跳转到指定的位置

      可以看到,Java 的异常处理是通过异常表的方式来决定代码执行的路径。而finally的实现是通过在每个路径的最后加入finally块中的字节码实现的。

      上述性能区别在哪?想当然就觉得try catch重复执行了这么多次肯定比只执行了一次跑得肯定慢,空间消耗肯定更大,实际情况是不是就是这样呢?
       从上小节字节码可知,每个类会跟随一张异常表(exception table),每一个try catch都会在这个表里添加行记录,每一个记录都有4个信息(try catch的开始地址,结束地址,异常的处理起始位,异常类名称)。
      当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表。。。以此类推。
      也就是说,异常如果没发生,也就不会去查表,也就是说你写不写try catch 也就是有没有这个异常表的问题,如果没有发生异常,写try catch对性能是木有消耗的,所以不会让程序跑得更慢。try 的范围大小其实就是异常表中两个值(开始地址和结束地址)的差异而已,也是不会影响性能的。

      当一个方法出现错误引发异常时,运行时系统创建异常对象,该对象包括了异常类型和堆栈信息等,接着运行时系统负责寻找合适的异常处理器(exception handler),潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合,当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,就是合适的异常处理器,运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

  • 对于这个try…catch块,他真正目的是捕获SQL的异常,但是这个try块包含了太多的信息了。这是我们为了偷懒而养成的代码坏习惯。有些人喜欢将一大块的代码全部包含在一个try块里面,因为这样省事,反正有异常抛出它就会捕获,而不愿意花时间来分析这个大代码块有哪几块会产生异常,产生什么类型的异常,反正就是一篓子全部搞定。这就想我们出去旅游将所有的东西全部装进一个箱子里面,而不是分类来装,虽然装进去容易,但找出来难!!!所有对于一个异常块,我们应该仔细分清楚每块的抛出异常,因为一个大代码块有太多的地方会出现异常了。所以应该尽可能减小try块范围!!!
  • 在这里你发现了什么?异常改变了运行流程!!如果该程序发生了异常那么conn.close(); out.close();是不可能执行得到的,这样势必会导致资源不能释放掉。所以如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,我们也要确保能够正确释放占用的资源。这里finally就有用武之地了:不管是否出现了异常,finally总会执行的,所以应当充分利用finally释放资源。
  • 使用这样代码的人都有这样一个心理,一个catch解决所有异常,这样是可以,但是不推荐!为什么!首先我们需要明白catch块所表示是它预期会出现何种异常,并且需要做何种处理。catch语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的Exception类。
  • 捕获异常不做处理,就是我们所谓的丢弃异常。我们都知道异常意味着程序出现了不可预期的问题,程序它希望我们能够做出处理来拯救它,但是你呢?一句ex.printStackTrace()搞定,这是多么的不负责任对程序的异常情况不理不顾。既然捕获了异常,就要对它进行适当的处理,比如:
    1. 对所发生的的异常进行一番处理,如修正错误、提醒.
    2. 重新抛出异常。既然你认为你没有能力处理该异常,那么你就尽情向上抛吧!!!
    3. 封装异常。对异常信息进行分类,然后进行封装处理。
    4. 打印出明确的异常信息。在出现异常后,我们最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。

我要回帖

更多关于 调用的目标发生了异常 的文章

 

随机推荐