`

异常(Exception)

    博客分类:
  • java
阅读更多

异常,是Java中非常常用的功能,它可以简化代码,并且增强代码的安全性。本文将介绍一些异常高级知识,也是学习Java一来的一次总结。包括以下内内容: 


异常的基础知识 
异常特点 
异常误用 
如何正确地使用异常 
异常的实现原理 


关于异常 

异常机制,是指程序不正常时的处理方式。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。 

异常的一般性语法为: 



    try { 
        // 有可能抛出异常的代码 
    } catch (Exception e) { 
        // 异常处理 
    } finally { 
        // 无论是否捕获到异常都会执行的程序 
    } 


Java异常体系 

Throwable类是整个Java异常体系的超类,都有的异常类都是派生自这个类。包含Error和Exception两个直接子类。 
Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA虚拟机出现错误。在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。 
Exception是应用层面上最顶层的异常类,包含RuntimeException(运行时异常)和 Checked Exception(受检异常)。 
RuntimeException是一种Unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。一般来说,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。常见的RuntimeException有NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException等。 
Checked Exception是相对于Unchecked Exception而言的,Java中并没有一个名为Checked Exception的类。它是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是Checked Exception。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。常用的Checked Exception有IOException、ClassNotFoundException等。 


异常的特点 

通用特点 

JVM捕获并处理未被应用程序捕获的异常 

无论是受检异常(Checked Exception)还是运行时异常(Runtime Exception),如果异常没有被应用程序捕获,那么最终这个异常会交由JVM来进行处理,会明显出现下面两个结果: 
1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。 
2. 异常栈信息会通过标准错误流输出。 



/** 
* 应用程序没有处理抛出的异常时,会交由JVM来处理这个异常。结果是: 
* 1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。 
* 2. 异常栈信息会通过标准错误流输出。 

* @author xialei 
* @version 1.0 2016年5月18日下午9:53:54 
*/ 
public class UncatchedException { 

    public static void main(String[] args) throws Exception { 
        throwException(); 
        System.out.println("这一行不会被打印出来"); 
    } 

    public static void throwException() throws Exception { 
        int i = 0; 
        if (i == 0) { 
            throw new Exception(); 
        } 
    } 




异常catch有顺序性 

在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch,catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。 



/* 
* 个人主页:http://hinylover.space 

* Creation Date: 2016年4月7日 下午2:29:42 
*/ 
package demo.blog.java.exception; 

/** 
* 在catch异常时,如果有多个异常,那么是会有顺序要求的。子类型必须要在父类型之前进行catch, 
* catch与分支逻辑是一致,如果父类型先被catch,那么后被catch的分支根本得不到运行机会。 

* @author xialei 
* @version 1.0 2016年5月18日下午10:00:40 
*/ 
public class ExceptionCatchOrder { 

    public void wrongCatchOrder() { 
        try { 
            Integer i = null; 
            int j = i; 
        } catch (Exception e) { 
        } catch (NullPointerException e) { // 编译不通过,eclipse提示“Unreachable catch block for NullPointerException. It is already handled by the catch block for Exception” 
        } 
    } 




异常被吃掉 

如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。这是一个非常值得注意的问题,因为异常信息是非常重要的,在出现问题时,我们通常凭它来查找问题。如果编码不小心而导致异常被吞噬,排查起来是相当困难的,这将是一个大隐患。 



/* 
* 个人主页:http://hinylover.space 

* Creation Date: 2016年4月7日 下午2:29:42 
*/ 
package demo.blog.java.exception; 

/** 
* 如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。 
* @author xialei 
* @version 1.0 2016年5月18日下午10:08:43 
*/ 
public class FinallySwallowException { 

    public static void main(String[] args) throws Exception { 
        System.out.println(swallowException()); // 打印出2,而不是打印出异常栈 
    } 

    public static int swallowException() throws Exception { 
        try { 
            throw new Exception(); 
        } finally { 
            return 2; 
        } 
    } 




重写Exception的fillInStackTrace()方法 

使用自定义异常时,可以重写fillInStackTrace()方法来控制Exception的异常栈信息。默认情况下,在程序抛出异常时,最终会通过调用private native Throwable fillInStackTrace(int dummy)这个本地方法来获取当前线程的堆栈信息,这是一个非常耗时的操作。如果我们仅仅需要用到异常的传播性质,而不关系异常的堆栈信息,那么完全可以通过重写fillInStackTrace()方法来实现。 



/* 
* 个人主页:http://hinylover.space 

* Creation Date: 2016年4月7日 下午2:29:42 
*/ 
package demo.blog.java.exception; 

/** 
* 重写Exception的fillInStackTrace()方法 

* @author xialei 
* @version 1.0 2016年5月18日下午10:18:57 
*/ 
public class MyException extends Exception { 

    public MyException(String message) { 
        super(message); 
    } 

    /* 
     * 重写fillInStackTrace方法会使得这个自定义的异常不会收集线程的整个异常栈信息,会大大 
     * 提高减少异常开销。 
     */ 
    @Override 
    public synchronized Throwable fillInStackTrace() { 
        return this; 
    } 

    public static void main(String[] args) { 
        try { 
            throw new MyException("由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。"); 
        } catch (MyException e) { 
            e.printStackTrace(); // 在控制台的打印结果为:demo.blog.java.exception.MyException: 由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。 
        } 
    } 




受检异常(checked exception) 



必须处理或者向上抛出 

我们必须要对底层抛出来的受检异常进行处理,处理方式有try...catch...或者向上抛出(throws),否则程序无法通过编译。 



package demo.blog.java.exception; 

/** 
* 必须对底层抛出的异常进行处理 
* @author xialei 
* @version 1.0 2016年5月18日下午10:42:53 
*/ 
public class CheckedException { 

    public static void main(String[] args) { 
        throwException(); // 编译不通过,必须对底层抛出的异常进行处理 
    } 

    public static void throwException() throws Exception { 
        throw new Exception(); 
    } 




不能捕获未被抛出的受检异常 

如果我们试图去捕获一个未被抛出的受检异常,程序将无法通过编译(Exception除外)。 



/* 
* 个人主页:http://hinylover.space 

* Creation Date: 2016年5月18日 下午10:45:32 
*/ 
package demo.blog.java.exception; 

import java.io.IOException; 

/** 
* 不能捕获一个没有被抛出的受检异常(Exception除外) 
* @author xialei 
* @version 1.0 2016年5月18日下午10:45:32 
*/ 
public class CantCatchUnthrowedException { 

    public void cantCatchUnthrowedException() { 
        try { 
            int i = 0; 
        } catch (IOException e) { // 编译不通过,eclipse提示:Unreachable catch block for IOException. This exception is never thrown from the try statement body 
            e.printStackTrace(); 
        } 
    } 




运行时异常(runtime exception) 

运行时异常(runtime exception)与受检异常(checked exception)的最大区别是不强制对抛出的异常进行处理。所有的运行时异常都继承自RuntimeException这个类,别问为什么,Java是这么规定的。与受检异常类似的例子,如果抛出的是运行时异常,就算不捕获这个异常,程序也可以编译通过。 



/* 
* 个人主页:http://hinylover.space 

* Creation Date: 2016年5月18日 下午11:02:57 
*/ 
package demo.blog.java.exception; 


/** 
* 编译通过 
* @author xialei 
* @version 1.0 2016年5月18日下午11:02:57 
*/ 
public class MyRuntimeException { 

    public void myRuntimeException() { 
        throw new RuntimeException(); // 可以正常编译 
    } 




异常的使用 



用受检异常还是运行时异常? 

在使用异常时,笔者经常为使用何种异常而犯难,在实际使用过程中总结了一些小经验。 



大概率发生时使用运行时异常 

从概率上来说,如果这个异常发生的频率非常高,那么因为使用运行时异常,最典型的就是NullPointException。Java中调用每个对象的方法时,都有可能会发生NullPointException。如果这是一个受检异常,那么在每次调用对象方法要么try {} catch {},那么使用throws关键字向上抛出。无论哪种方式,代码无疑都会是非常丑陋的,那画面太“美”不敢看。如果代码里充斥着各种异常处理块,可读性将会大打折扣。 



异常无法恢复时使用运行时异常 

当异常发生时,如果开发者无法从异常状态恢复到正常状态,那么这种异常应该是运行时异常。如果使用受检异常,这除了加重开发者的负担之外,别无它用。当在调用其他方法时,如果方法抛出受检异常,那么笔者就会比较紧张。因为这意味着需要停止业务逻辑开发,然后开始思考如何处理这该死的异常。运行时异常通常是由于开发者编程不当所引起的,譬如空指针异常、除零异常等。如果开发者在开发过程中小心谨慎,考虑周全,就可能避免这种异常的发生。 



可恢复时优先使用受检异常 

如果我们能够从异常中恢复到正常状态,那么应该优先使用受检异常。为什么是优先而不是一定呢?因为从原理上来说,使用运行时异常也可以恢复到正常状态,而且使用运行时异常的代码无疑会比较干净整洁。而使用受检异常,明确地说明了调用方式时可能发生异常情况,强制开发者去处理这些异常情况常常会增强代码的健壮性。受检异常通常是由外部环境所引起的,譬如IOException等。 



使用受检异常做流程控制 

从Java语义上来说,应该是当程序层面真正发生异常状况时才应该使用异常(Exception),《Effect Java》一书中也建议只有真正的异常情况才使用异常。但我们有时也会利用异常来达到业务流程控制的目的。这样做主要有下面的好处: 


简化代码逻辑。我们无需为多分枝业务流程编写各种if...else...语句来处理不同的情况。相反地,我们只需处理正常的业务流程即可,异常流程只需要通过异常向上抛出去即可,至于谁去处理这些异常,则不需要我们过多地关心。
可读性增强。如果一段代码中充斥着分枝逻辑,那么整个代码的可读性会非常差。在阅读代码时,很难理清楚代码的主干。说到底,主干代码才是我们重点关注的。如果使用异常进行流程控制,主干代码就清晰地显示在面前,两个字:舒服! 




尽量集中处理异常 

在各种有关代码重构的书本中,都会提到一个核心原则:一个方法应该仅做一件事情。如果一个方法中,既包含业务逻辑,又包含异常处理程序,那么实际上这个方法就做了两件事情。如果异常上层可以处理,那么就不应该在下层处理。在上层进行处理的好处是,可以对异常进行统一地处理。而至于将异常处理程序分散到代码的各个地方,导致维护起来十分困难。在进行异常处理时,应该优先考虑使用AOP(面向切面编程)技术,这样降低了核心业务逻辑与异常处理的耦合性。 



自定义异常体系 

在应用系统中应该要建立自己的异常体系,这样便于统一处理系统中出现的异常。笔者在开发过程中,通常会建立类似下图所示的系统体系。越靠近底层,越使用更加底层的、具体的异常。如果是其他系统中的异常(譬如Java自身的异常),也应该将其转化为自定义体系中的对应异常。 



异常误用 


e.printTrace()处理所有异常。 

使用e.printTrace()来粗暴地处理所有异常是新手经常犯的毛病,笔者在初学时也是这么干的。为什么会出现这种情况呢?因为如果未处理的受检异常,代码将编译不通过,IDE(如eclipse)中会无情地打上各种红叉叉。这是IDE可以帮我们处理的情况,于是按照IDE的提示,try{}catch{}这段代码,其默认的异常处理就是调用e.printTrace()方法。初学者只顾着代码顺利通过编译,而完全没有考虑这样做存在的风险。这样做的风险如下: 


错失正确的处理方法。相当一部分受检异常通过正确地处理是可以恢复正常,简单粗暴地使用e.printTrace()将错失恢复机会。 
“丢失”异常信息。printTrace()方法是Throwable类中的一个方法,它的作用是将异常栈信息打印到标准错误流中。如果Web项目,会将异常信息打印到容器的日志文件中;如果是普通项目,通常会将标准输出和标准错误重定向到dev/null(空设备)中。无论是何种情况,都有可能导致异常信息“丢失”(web容器中的日志其实不代表丢失了,但是我们通常不会利用容器级别的日志排错),给排错带来很大的麻烦。 


全部使用运行时异常 

为了“偷懒”,无论什么情况都使用运行时异常,这样就可以不用费劲处理异常了,轻轻松松。但是,现在没事不代表以后不出事,这无疑为代码埋下了隐患。如果在调用一个方法时,该方法并没有显示地抛出异常,也没有在javadoc中强调,我们就不会知道调用这段代码可能发生的异常情况,那些可能出现的异常情况对我们来说是透明的。 



总是catch Exception对象 

在捕获和处理异常时,不管3721,一股脑地catch Exception对象。没错,这样就可以一次性处理所有异常情况。但是,所有地异常都使用相同的处理程序真的对吗?在这样做之前应该先要打个大大的问号。这样做的后果是我们会无形中忽略那些重要的异常。 



正确使用异常 


正确地处理异常。针对不用的异常采取合适的、正确的异常处理方式,不要遇到任何异常都printTrace()或者打印一个日志。 
catch时指定具体的异常。不要一股脑地catch Exception,具体的异常应该单独catch住,越具体的异常越早catch。 
涉及到资源时,需要finally。如果涉及到资源的关闭时,应该将关闭资源的代码写在finally代码块内。 
最小化try{ } catch{ }范围。try的范围应该尽量小,最好就是try住抛出异常的那个方法即可。 



异常的实现原理(字节码级别) 

从字节码层面上来分析一下Java异常的实现原理,编写如下所示的源代码,使用javac命令进行编译,然后使用javap命令查看编译后的字节码细节内容。 



public class ExceptionClassCode { 

    public int demo() { 
        int x; 
        try { 
            x = 1; 
            return x; 
        } catch (Exception e) { 
            x = 2; 
            return x; 
        } finally { 
            x = 3 
        } 
    } 




public int demo(); 
    descriptor: ()I 
    flags: ACC_PUBLIC 
    Code: 
      stack=1, locals=5, args_size=1 
         0: iconst_1 // 生成整数1 
         1: istore_1 // 将生成的整数1赋予第1号局部变量(x=1) 
         2: iload_1 // 将x(=1)的值入栈 
         3: istore_2 // 将栈顶的值(=1)赋予第2号变量(returnValue) 
         4: iconst_3 // 生成整数3 
         5: istore_1 // x=3 
         6: iload_2 // returnValue=当前栈顶值(=1) 
         7: ireturn // 返回returnValue(=1) 
         8: astore_2 // 将Exception对象引用值赋予第2号局部变量 
         9: iconst_2 // 生成整数2 
        10: istore_1 // x=2 
        11: iload_1 // x(=2)压入栈顶 
        12: istore_3 // 将栈顶的值(=2)赋予第3号变量(returnValue) 
        13: iconst_3 // 生成整数3 
        14: istore_1 // x=3 
        15: iload_3  // returnValue(=2)压入栈顶 
        16: ireturn  // 返回returnValue(=2) 
        17: astore        4 // 将异常信息保存到第4号局部变量 
        19: iconst_3 // 生成整数3 
        20: istore_1 // x=3 
        21: aload         4 // 将异常引用值压入栈 
        23: athrow // 抛出栈顶所引用的异常 
      Exception table: 
         from    to  target type 
             0     4     8   Class java/lang/Exception # 如果0~4行字节码(try代码块)中出现Exception及其子类异常,则执行第8行(catch代码行) 
             0     4    17   any # 无论0~4行字节码(try代码块)是否抛出异常,都执行第17行(finally代码行) 
             8    13    17   any # 无论8~13行字节码(catch代码块)是否抛出异常,都执行第17行(finally代码行) 
            17    19    17   any 123456789101112131415161718192021222324252627282930313233 

看到字节码中有一个Exception table(异常表)区域,这个就是与异常相关的字节码内容。它表示在from到to所指示的字节码行中,如果抛出type所对应的异常(及其子类),那么就跳到target指定的字节码行开始执行。

 

 

本文转自:http://maosheng.iteye.com/blog/2431851

分享到:
评论

相关推荐

    JAVA 自定义异常 Exception

    这就是“JAVA 自定义异常 Exception”所涉及的内容。 自定义异常是在Java中通过继承`java.lang.Throwable`或其子类(如`Exception`或`Error`)来实现的。通常,我们选择继承`Exception`类,因为它用于表示程序可以...

    c# 自定义异常 Exception

    `Exception`类是所有系统异常的基类,包含了描述错误的基本属性,如`Message`(错误消息)和`InnerException`(内部异常)。以下是一个简单的自定义异常类的示例: ```csharp public class CustomException : ...

    Java异常 Exception类及其子类(实例讲解)

    Java异常Exception类及其子类是Java语言中异常处理的核心部分。异常是指程序在执行过程中出现的非正常情况,例如除数为零、数组索引越界、类型转换错误等。Java语言所有的异常都可以用一个类来表示,不同类型的异常...

    java异常exception

    `ArithmeticException`是在执行某些算术运算时发生的异常,如除以零或整数溢出等。这类异常通常出现在程序逻辑中对数值操作不当的情况,比如进行数学计算时未对分母是否为零进行检查。为了避免这类异常,可以在执行...

    java异常 exception

    - **RuntimeException 及其子类**:这类异常通常是由于程序设计问题引起的,如数组越界 (`ArrayIndexOutOfBoundsException`) 或除零错误 (`ArithmeticException`)。 #### 四、异常处理的基本结构 异常处理的核心...

    ASP.NET MVC中异常Exception拦截的深入理解

    本文将深入探讨ASP.NET MVC中异常Exception拦截的机制和实现。 一、异常处理的重要性 在开发Web应用时,由于各种不可预知的因素,如用户输入错误、网络问题或服务器故障,可能会引发异常。这些异常如果未经处理,...

    exception 异常处理 exception

    自定义异常是根据业务需求自定义的一类异常,通过继承 `Exception` 或其子类实现。在上述代码示例中,定义了一个名为 `MyException` 的自定义异常类,它继承了 `Exception`。 - **定义自定义异常类**: ```java ...

    Retrofit2+Rxjava2网络请求异常统一封装处理

    exception = new ApiException(-1, "网络连接异常"); } else { // 其他未知异常 exception = new ApiException(-1, "服务器内部错误"); } listener.onError(exception); }); } } ``` 在这个工具类中,我们...

    高效的java异常处理框架高效的java异常处理框架高效的java异常处理框架

    其中异常类 Exception 又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。 二、异常的捕获和处理 Java ...

    UncaughtException不让Android应用异常退出

    "UncaughtException不让Android应用异常退出"这个主题,就是关于如何处理程序中的未捕获异常,以防止应用突然崩溃,从而提升用户体验。在这个问题上,我们可以从以下几个方面来探讨: 1. **...

    Java异常处理及应用.docx

    其中异常类 Exception 又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。 Error 是程序无法处理的错误,...

    java 除0发生的算术异常(ArithmeticException)

    总的来说,`ArithmeticException`是Java中常见的运行时异常之一,通过合理的异常处理和输入验证,我们可以避免这类异常的发生,提高程序的健壮性。理解并妥善处理这种异常对于编写稳定且可靠的Java程序至关重要。

    C++Exception 异常处理 源码

    5. **异常规格(Exception Specifications)**: 可以在函数声明中使用`noexcept`关键字指定函数是否可能会抛出异常。这有助于编译器进行优化,并可以在违反规格时引发`std::terminate()`。 6. **异常安全**: 在...

    android全局异常捕获 exception_global

    标题"android全局异常捕获 exception_global"和描述都指向了这个主题,表明我们将探讨如何在Android应用程序中实现全面的异常处理。 全局异常捕获通常涉及到创建一个全局的错误处理器,它能在应用程序的任何地方...

    异常示例(Exception)

    异常(Exception)是程序运行时出现的错误情况,它中断了正常的代码执行流程。本篇文章将深入探讨Java异常处理机制,以及如何利用提供的三个示例文件(demo2、demo3、demo1)来理解这一概念。 Java异常处理基于五个...

    异常日志捕获ExceptionLog

    标题提到的“异常日志捕获ExceptionLog”是一种常见的实践,旨在记录应用运行过程中的错误和异常信息,以便后续分析和调试。 在AndroidManifest.xml文件中,我们通常会在`<application>`节点下添加自定义的应用类...

    c++异常处理exception.rar

    异常类通常是继承自`std::exception`的类,提供了基本的错误信息和处理方式。 2. `try`块: `try`块是用来包含可能抛出异常的代码的。如果在`try`块内的代码抛出了异常,程序会立即停止执行`try`块内的剩余代码,...

    Oracle_存储过程exception异常处理大全及实例经典最终.docx

    ### Oracle 存储过程 Exception 异常处理详解 #### 1. 异常处理概念 在 Oracle 存储过程中,异常处理是一项非常重要的技术,它能够帮助开发人员处理那些不可预见的情况,确保程序的健壮性和稳定性。异常处理主要...

    异常处理流程图Exception

    本文将深入探讨异常处理流程,通过分析给定的“异常处理流程图Exception”来理解其核心概念。 ### 异常处理概述 异常处理是一种编程模式,用于捕捉和响应程序运行时可能发生的非正常事件,这些事件被称为异常。在...

    第十八章异常(Exception)处理

    异常(Exception)是Java中一种特殊的运行错误对象,它代表了程序执行期间发生的非正常情况,需要进行处理以避免程序中断。 18.1 异常类结构与组成 Java中的异常都源自`java.lang.Throwable`类。Throwable有两个...

Global site tag (gtag.js) - Google Analytics