• 如何正确使用Java异常处理机制


    第一节 异常处理概述

    第二节 Java异常处理类

    • 2.1 Throwable

      • 2.1.1 Throwable有五种构造方法

      • 2.1.2 Throwable的所有成员方法

    • 2.2 Error

    • 2.3 Exception

    • 2.4 RuntimeException

    • 2.5 Checked Exception

    • 2.6 Uncheck Exception

    • 2.7 总结

    第三节 Java异常处理执行流程探究

    • 3.1 流程一

    • 3.2 流程二

    • 3.3 流程三

    • 3.4 流程四

    • 3.5 流程五

    • 3.6 流程六

    • 3.7 流程七

    • 3.8 流程八

    • 3.9 总结

    第四节 Java异常处理实践原则

    • 4.1 使用异常,而不使用返回码

    • 4.2 利用运行时异常设定方法使用规则

    • 4.3 消除运行时异常

    • 4.4 正确处理检查异常

    • 4.5 使主流程代码保持整洁

    • 4.6 使用try-with-resources

    • 4.7 尽量处理最具体的异常

    • 4.8 设计自己的异常类型要遵循的原则

      本文的目标并不是介绍Java异常处理机制相关概念和语法,如果你有这方面的需求,请参考我的“ Java异常 官方文档翻译系列”文章。本文的目标是如何正确使用Java异常处理机制。

    第一节 异常处理概述

      在理想境界中,程序永远不会出现问题,用户输入的数据永远是正确的,逻辑没有任何问题 ,选择打开的文件也一定是存在的,内存永远是够用的……反正没有任何问题!但是一旦出现这些问题,如果处理不好,程序就不能正常运行了,用户就有可能再也不使用这个程序了。

      要处理异常,必先知道异常发生的原因;要知道异常发生的原因,必先知道异常发生的场景。你的程序可能和任何其他实体交互的时候,都可能发生异常。Java程序中,都是利用方法(Method)和其他实体进行交互。所以异常的发生、抛出、声明和处理都是在方法内。下图是Java程序可能和其他实体交互的概要图:

    图1:你的方法与其他实体交互概要图

    如图1所示,你写的方法和外部实体交互大概可以分为五类:

    1. 和资源(Resource)交互,见图⑤处。这里资源的范围很广,比如进程外部的数据库,文件,SOA服务,其他各种中间件;进程内的类,方法,线程……都算是资源。
    2. 给进程内的其他方法(User Method)提供服务,见图②处。
    3. 依赖进程内的其他方法(Server Method),见图③处。包括Java平台提供的方法和其他第三方供应方提供的方法。
    4. 和系统环境交互,见图⑧处。系统环境可能是直接环境——JVM,也可能是间接环境——操作系统或硬件等。
    5. 给外部实体提供服务,见图①处。这种外部实体一般会通过容器(或其他类似的机制)和你的方法进行交互。所以,可以归为②,不予探讨。

    Java方法和每一类实体进行交互时,都可能发生异常。

    当和资源交互时,常常会因为资源不可用而发生异常,比如发生找不到文件、数据库连接错误、找不到类、找不到方法……等等状况。有可能是直接产生的,见图⑤处;有可能是间接产生的,比如图⑥处发生异常,Server Method把异常抛给Your Method,图③处就间接发生了异常。一般来说,你写的方法间接发生这类异常的可能性比直接发生要大得多,因为直接产生这类异常的方法在Java平台中已经提供了。对于这类异常,通常有以下几个特点:

    • 问题来自外部,你的方法本身的正常流程逻辑没问题。
    • 这类异常通常是暂时的,过段时间就可用了(经过资源维护者的彻夜奋战……)。最终用户通常也可以接受暂时的等待或采取替补方案。
    • 你的程序的其他功能还可以用。

    这时,你的方法应该这样处理:

    • 返回到一种安全状态,并能够让用户执行一些其他的命令(比如支付宝支付失败时返回一个弹出框,说明余额不足等原因,让用户重新选择其他支付渠道);或者
    • 允许用户保存所有操作的结果,并以适当的方式终止程序(比如保存填了一半的表单)。

    然后,你应该协调各方,促进资源恢复可用,消除异常。

    当给用户方法(User Method )提供服务时,用户可能会传入一些不合法的数据(或者其他不恰当的使用方法),进而对程序的正常流程造成破坏。你的方法应该检查每一个输入数据,如果发现不合法的数据,马上阻止执行流程,并通知用户方法。

    当调用服务方法(Server Method )时,有可能会发生两类异常。一类是你的使用方法不正确,导致服务中止;一类是服务方法出了异常,然后传递给你的方法。如果是第一种异常,你应该检查并修改你的方法逻辑,消除BUG。对于第二类异常,你要么写一个处理器处理,要么继续传递给上层方法。

    当和系统环境交互时,有可能因为JVM参数设置不当,有可能因为程序产生了大量不必要的对象,也有可能因为硬故障(操作系统或硬件出了问题),导致整个程序不可用。当这类异常发生时,最终用户没法选择其他替代方案,操作到一半的数据会全部丢失。你的方法对这类异常一般没什么办法,既不能通过修改主流程逻辑来消除,也不能通过增加异常处理器来处理。所以通常你的方法对这类异常不需要做任何处理。但是你必须检查进程内的所有程序和系统环境是否正常,然后协调各方,修改BUG或恢复环境。

    Java的异常都是发生在方法内,所以研究Java异常,要以你设计的方法为中心。我们以“你的方法 ”为中心,总结一下处理办法:当服务方法告诉“你的方法 ”的主流程逻辑有问题时,就要及时修复BUG来消除异常;当用户方法非法使用“你的方法”时,应该直接中止主流程,并通知用户方法,强迫用户方法使用正确的方式,防止问题蔓延;当服务方法传递一个异常给“你的方法”时,你要判断“你的方法”是否合适处理这个异常,如果不合适,传递给上层方法,如果合适,写一个异常处理器处理这个异常。当系统环境出了问题,“你的方法”什么也做不了。

    刚才以“你的方法”为中心,总结了在“你的方法”内部的处理办法。现在以“你”为中心,总结一下方法外部的处理方法:当资源不可用的时候,你应该协调各方,恢复资源;当发生系统故障时,你应该协调各方,恢复系统。

    现在,已经基本分析清楚了异常发生的原因,以及相应的应对方法。下一节正式介绍Java异常处理机制。

    第二节 Java异常处理类

    Java把异常当做是破坏正常流程的一个事件,当事件发生后,就会触发处理机制。

    Java有一套独立的异常处理机制,在遇到异常时,方法并不返回任何值(返回值属于正常流程),而是抛出一个封装了错误信息的对象。下图是Java异常处理机制类层级结构图:

    图2:Java异常处理机制类层级结构图

    2.1 Throwable

    所有的异常对象都派生于Throwable类的一个实例。

    2.1.1 Throwable有五种构造方法:

    备注:

    • suppression:被压抑的异常。想了解更多信息,请参看我的译文“try-with-resources语句”。
    • strack trace:堆栈跟踪。是一个方法调用过程列表,它包含了程序执行过程中方法调用的具体位置。

    2.1.2 Throwable的所有成员方法:

    备注:

    • 所有派生于Throwable类的异常类,基本都没有这些成员方法,也就是说所有的异常类都只是一个标记,记录发生了什么类型的异常(通过标记,编译期和JVM做不同的处理),所有实质性的行为Throwable都具备了。
    • 综上,在一个Throwable里面可以获取什么信息?
      • 获取堆栈跟踪信息(源代码中哪个类,哪个方法,第几行出现了问题……从当前代码到最底层的代码调用链都可以查出来)
      • 获取引发当前Throwable的Throwable。追踪获取底层的异常信息。
      • 获取被压抑了,没抛出来的其他Throwable。一次只能抛出一个异常,如果发生了多个异常,其他异常就不会被抛出,这时可以通过加入suppressed异常列表来解决(JDK7以后才有)。
      • 获取基本的详细描述信息

    从图2可以看出,Throwable类只有两个直接继承者:Error和Exception。然后Exception又分为RuntimeException和Checked Exception。

    2.2 Error

    在Java中, 由系统环境问题引起的异常,一般都继承于Error类。

    对于Error类:

    • 一般开发者不要自定义Error子类,因为它代表系统级别的错误。与一般的程序无关。
    • 在Java异常处理机制中,Error不强制捕获或声明,也就是不强制处理。因为程序本身对此类错误无能为力。一般情况下我们只要把堆栈跟踪信息记录下来就行。

    下列是Java平台中直接继承于Error的错误类型:

    AnnotationFormatErrorAssertionErrorAWTErrorCoderMalfunctionErrorFactoryConfigurationError,FactoryConfigurationErrorIOErrorLinkageErrorServiceConfigurationErrorThreadDeath,TransformerFactoryConfigurationErrorVirtualMachineError

    2.3 Exception

    在Java中,除了系统环境问题引起的异常,一般都继承于Exception类。Exception分为RuntimeException和Checked Exception。Checked Exception必须要捕获或声明。而RuntimeException不强制。

    对于Exception类:

    • 如果你创建了一个异常类型,直接继承于Exception,那么这个异常类型将属于检查异常(Checked Exception)。

    2.4 RuntimeException

    在Java中,由于接口方法使用不当造成的异常,一般属于RuntimeException,也就是运行时异常。

    对于RuntimeException:

    • 如果你调用服务方法的方式不正确,你应该马上修改代码,避免发生RuntimeException
    • 如果是用户方法调用你的方法的方式不正确,你应该立刻抛出RuntimeException,强制让使用者修正代码或改变使用方式,防止问题蔓延
    • 一般情况下,不要捕获或声明RuntimeException。因为问题在于你的程序本身有问题,如果你用异常流程处理了,反而让正常流程问题一直存在

    下列是Java平台中直接继承于RuntimeException的运行时异常:

    AnnotationTypeMismatchExceptionArithmeticExceptionArrayStoreExceptionBufferOverflowException,BufferUnderflowExceptionCannotRedoExceptionCannotUndoExceptionClassCastException,CMMExceptionConcurrentModificationException,DataBindingExceptionDOMException,EmptyStackExceptionEnumConstantNotPresentExceptionEventException,FileSystemAlreadyExistsExceptionFileSystemNotFoundExceptionIllegalArgumentException,IllegalMonitorStateException,IllegalPathStateExceptionIllegalStateExceptionIllformedLocaleException,ImagingOpException,IncompleteAnnotationExceptionIndexOutOfBoundsException,JMRuntimeExceptionLSExceptionMalformedParameterizedTypeException,MirroredTypesException,MissingResourceExceptionNegativeArraySizeException,NoSuchElementExceptionNoSuchMechanismExceptionNullPointerExceptionProfileDataException,ProviderExceptionProviderNotFoundExceptionRasterFormatExceptionRejectedExecutionException,SecurityExceptionSystemExceptionTypeConstraintExceptionTypeNotPresentException,UndeclaredThrowableExceptionUnknownEntityExceptionUnmodifiableSetException,UnsupportedOperationExceptionWebServiceExceptionWrongMethodTypeException

    2.5 Checked Exception

    在Java中,直接或间接因为“资源”问题引起的异常,一般属于检查异常(Checked Exception) 。检查异常继承于Exception,而不继承于RuntimeException。

    对于检查异常:

    • 必须捕获或声明
    • 交给关心这个异常的方法处理
    • 异常处理器应该引导用户接下来怎么办,至少做到安全退出

    下列是Java平台中直接继承于Exception的检查异常:

    AclNotFoundExceptionActivationExceptionAlreadyBoundExceptionApplicationExceptionAWTException,BackingStoreExceptionBadAttributeValueExpExceptionBadBinaryOpValueExpException,BadLocationExceptionBadStringOperationException,BrokenBarrierExceptionCertificateException,CloneNotSupportedExceptionDataFormatExceptionDatatypeConfigurationExceptionDestroyFailedException,ExecutionExceptionExpandVetoExceptionFontFormatExceptionGeneralSecurityException,GSSException,IllegalClassFormatExceptionInterruptedExceptionIntrospectionExceptionInvalidApplicationException,InvalidMidiDataExceptionInvalidPreferencesFormatExceptionInvalidTargetObjectTypeException,IOExceptionJAXBExceptionJMException,KeySelectorExceptionLastOwnerException,LineUnavailableExceptionMarshalExceptionMidiUnavailableExceptionMimeTypeParseException,MimeTypeParseExceptionNamingExceptionNoninvertibleTransformExceptionNotBoundException,NotOwnerExceptionParseExceptionParserConfigurationExceptionPrinterExceptionPrintException,PrivilegedActionExceptionPropertyVetoExceptionReflectiveOperationExceptionRefreshFailedException,RemarshalExceptionSAXException,ScriptExceptionServerNotActiveExceptionSOAPException,SQLExceptionTimeoutExceptionTooManyListenersExceptionTransformerExceptionTransformException,UnmodifiableClassExceptionUnsupportedAudioFileExceptionUnsupportedCallbackException,UnsupportedFlavorExceptionUnsupportedLookAndFeelExceptionURIReferenceExceptionURISyntaxExceptionUserExceptionXAExceptionXMLParseExceptionXMLSignatureException,XMLStreamExceptionXPathException

    2.6 Uncheck Exception

    Error和RuntimeException统称为非检查异常。两者的共同点就是都不被强制捕获或声明。实际上两者描述问题的范围完全没有交集。

    2.7 总结

    所有的功能都在Throwable类里面实现了,子类只需要直接继承或间接继承它,并且加上需要的构造方法就行(一般而言,第一第二个构造方法是必须的,也可以全部加上),而且构造方法通常只需要一行代码:super(...),也就是说只要调用父类的构造方法就行了。Java把异常分为三类(Error,Checked Exception,RuntimeException),只是在语法层面上有不同的标记而已。它们自身拥有的功能一样,运行时系统处理它们的方式也是一样的(你也可以捕获或声明非检查异常),不同的是编译器对它们的区别对待(检查异常必须要在代码里处理,非检查异常就不需要),以及程序员对它们的区别对待(这需要程序员遵循良好的实践原则)。

    这三类异常全部覆盖了第一节中所描述的异常发生场景,图1中,④⑤⑥处可能会发生Checked Exception,②③处既可能会发生RuntimeException也可能会发生Checked Exception,⑦⑧⑨处可能会发生Error。①处已经超出了Java异常处理机制的范畴(这属于容器要考虑的问题),通常在数据中加入返回码来通知异常信息。

    理解了每一类异常对应的场景,很多人其实已经知道该怎么用了,不必往下看了。

    第三节 Java异常处理执行流程探究

    首先设计两个方法,一个方法可能会抛出RuntimeException,一个方法可能会抛出Checked Exception。

     1   public String runtimeServerMethod(String s) {
     2     if(s==null) {
     3        throw new RuntimeException("runtimeServerMethod方法的字符串不能为空")
     4     }
     5     return s;
     6   }
    7   private BufferedReader bufferedReader; 8   public String checkedServerMethod(String s) throws IOException { 9   File file = new File(s); 10   Reader reader = new FileReader(file); 11   bufferedReader = new BufferedReader(reader); 12   String result = bufferedReader.readLine(); 13   return result; 14   }

    3.1 流程一

    1 public void currentMethod() {
    2   System.out.println("--------------------try-catch-before");
    3   String result = this.runtimeServerMethod(null);
    4   System.out.println("--------------------result:"+result);
    5   System.out.println("--------------------try-catch-after");
    6 }

    执行结果:

    1 --------------------try-catch-before
    2 Exception in thread "main" java.lang.RuntimeException: runtimeServerMethod方法的字符串不能为空
    3      at com.leaforbook.javaexception.App.runtimeServerMethod(App.java:34)
    4      at com.leaforbook.javaexception.App.currentMethod(App.java:26)
    5      at com.leaforbook.javaexception.App.userMethod(App.java:20)
    6      at com.leaforbook.javaexception.App.main(App.java:16)

    分析:

      违反了runtimeServerMethod方法的使用规则——入参不能为null,导致产生了一个运行时异常。主流程线程直接中断,后面的代码不再执行。

    3.2 流程二

     1   public void currentMethod() {
     2     System.out.println("--------------------try-catch-before");
     3     String result = null;
     4     try {
     5        result = this.runtimeServerMethod(null);
     6        System.out.println("--------------------result:"+result);
     7     } catch (Exception e) {
     8        System.out.println("--------------------in-catch");
     9     }
    10     System.out.println("--------------------try-catch-after");
    11   }

    执行结果:

    1 --------------------try-catch-before
    2 --------------------in-catch
    3 --------------------try-catch-after

    分析:

      RuntimeException也可以捕获处理(这是一种不好的实践),运行时系统并不会区分异常类型。异常发生以后,try代码块后面的代码不再执行,而是跳到catch代码块,线程不中断,执行完整个方法。

    3.3 流程三

    1   public void currentMethod() {
    2      System.out.println("--------------------try-catch-before");
    3      String result = null;
    4      result = this.runtimeServerMethod("Conform to the rules");
    5      System.out.println("--------------------result:"+result);
    6      System.out.println("--------------------try-catch-after");
    7   }

    执行结果:

    1 --------------------try-catch-before
    2 --------------------result:Conform to the rules
    3 --------------------try-catch-after

    分析:

      当符合服务方法的规则时,就不会抛出运行时异常。方法就可以正常执行完成。

    3.4 流程四

     1   public void currentMethod() {
     2        System.out.println("--------------------try-catch-before");
     3        String result = null;
     4        try {
     5                 result = this.checkedServerMethod("");
     6                  System.out.println("--------------------result:"+result);
     7             } catch (IOException e) {
     8                 System.out.println("--------------------in-catch");
     9             } finally {
    10                  System.out.println("--------------------in-finally");
    11                 if(bufferedReader!=null) {
    12                      try {
    13                           bufferedReader.close();
    14                      } catch (IOException e) {
    15                           e.printStackTrace();
    16                      }
    17                 }
    18             }
    19        System.out.println("--------------------try-catch-after");
    20   }

    执行结果:

    1 --------------------try-catch-before
    2 --------------------in-catch
    3 --------------------in-finally
    4 --------------------try-catch-after

    分析:

    当调用了checkedServerMethod方法,并且发生了Checked Exception时,一定要捕获或声明该异常,否则编译不通过。上例中,异常发生后,try代码块后面的代码不再执行,跳到catch代码块,再执行finally代码块(在这里有关闭资源的操作),然后再执行其余部分。

    3.5 流程五

     1 public void currentMethod() {
     2      System.out.println("--------------------try-catch-before");
     3      String result = null;
     4      try {
     5               result = this.checkedServerMethod("");
     6                System.out.println("--------------------result:"+result);
     7           } catch (IOException e) {
     8               System.out.println("--------------------in-catch");
     9               return;
    10           } finally {
    11                System.out.println("--------------------in-finally");
    12               if(bufferedReader!=null) {
    13                    try {
    14                         bufferedReader.close();
    15                    } catch (IOException e) {
    16                         e.printStackTrace();
    17                    }
    18               }
    19           }
    20      System.out.println("--------------------try-catch-after");
    21  }

    执行结果:

    1 --------------------try-catch-before
    2 --------------------in-catch
    3 --------------------in-finally

    分析:

      和流程四不同之处在于,catch代码块多了一条return语句。执行结果也相应发生了变化,try-catch-finally代码块后面的代码不再执行。而且值得注意的是,finally代码块的代码依然执行了。这就是finally代码块的意义。

    3.6 流程六

     1   public void userMethod() {
     2       try {
     3           this.currentMethod();
     4       } catch (IOException e) {
     5           e.printStackTrace();
     6       }
     7   }
     8   public void currentMethod() throws IOException {
     9        System.out.println("--------------------try-catch-before");
    10        String result = null;
    11        try {
    12               result = this.checkedServerMethod("");
    13               System.out.println("--------------------result:"+result);
    14           } catch (IOException e) {
    15               System.out.println("--------------------in-catch");
    16               this.checkedServerMethod("catch");
    17           } finally {
    18                System.out.println("--------------------in-finally");
    19               if(bufferedReader!=null) {
    20                    try {
    21                         bufferedReader.close();
    22                    } catch (IOException e) {
    23                         e.printStackTrace();
    24                    }
    25               }
    26           }
    27          System.out.println("--------------------try-catch-after");
    28 }

    执行结果:

     1 --------------------try-catch-before
     2 --------------------in-catch
     3 --------------------in-finally
     4 java.io.FileNotFoundException: catch (系统找不到指定的文件。)
     5      at java.io.FileInputStream.open0(Native Method)
     6      at java.io.FileInputStream.open(FileInputStream.java:195)
     7      at java.io.FileInputStream.<init>(FileInputStream.java:138)
     8      at java.io.FileReader.<init>(FileReader.java:72)
     9      at com.leaforbook.javaexception.App.checkedServerMethod(App.java:62)
    10      at com.leaforbook.javaexception.App.currentMethod(App.java:38)
    11      at com.leaforbook.javaexception.App.userMethod(App.java:23)
    12      at com.leaforbook.javaexception.App.main(App.java:18)

    分析:

      和流程五类似,只不过catch代码块不是返回一个正常值,而是抛出一个Checked Exception。

    3.7 流程七

     1   public void userMethod() {
     2     try {
     3         this.currentMethod();
     4     } catch (IOException e) {
     5         e.printStackTrace();
     6     }
     7   }
     8   public void currentMethod() throws IOException {
     9        System.out.println("--------------------try-catch-before");
    10        String result = null;
    11        try {
    12               result = this.checkedServerMethod("");
    13                System.out.println("--------------------result:"+result);
    14           } catch (IOException e) {
    15               System.out.println("--------------------in-catch");
    16             this.checkedServerMethod("catch");
    17           } finally {
    18                System.out.println("--------------------in-finally");
    19               if(bufferedReader!=null) {
    20                    try {
    21                         bufferedReader.close();
    22                    } catch (IOException e) {
    23                         e.printStackTrace();
    24                    }
    25               }
    26               this.checkedServerMethod("finally");
    27           }
    28       System.out.println("--------------------try-catch-after");
    29 }

    执行结果:

     1 --------------------try-catch-before
     2 --------------------in-catch
     3 --------------------in-finally
     4 java.io.FileNotFoundException: finally (系统找不到指定的文件。)
     5      at java.io.FileInputStream.open0(Native Method)
     6      at java.io.FileInputStream.open(FileInputStream.java:195)
     7      at java.io.FileInputStream.<init>(FileInputStream.java:138)
     8      at java.io.FileReader.<init>(FileReader.java:72)
     9      at com.leaforbook.javaexception.App.checkedServerMethod(App.java:63)
    10      at com.leaforbook.javaexception.App.currentMethod(App.java:48)
    11      at com.leaforbook.javaexception.App.userMethod(App.java:23)
    12      at com.leaforbook.javaexception.App.main(App.java:18)

    分析:

      和流程六比起来,流程七在finally代码块也抛出了一个异常,最终在userMethod方法里面捕获到的是finally代码块的异常,catch代码块里抛出的异常被压抑了。

    3.8 流程八

     1   public void userMethod() {
     2        try {
     3            this.currentMethod();
     4        } catch (IOException e) {
     5            e.printStackTrace();
     6        }
     7   }
     8   public void currentMethod() throws IOException {
     9        System.out.println("--------------------try-catch-before");
    10        String result = null;
    11        try {
    12               result = this.checkedServerMethod("");
    13               System.out.println("--------------------result:"+result);
    14           }finally {
    15                System.out.println("--------------------in-finally");
    16               if(bufferedReader!=null) {
    17                    try {
    18                         bufferedReader.close();
    19                    } catch (IOException e) {
    20                         e.printStackTrace();
    21                    }
    22               }
    23           }
    24      System.out.println("--------------------try-catch-after");
    25  }

    执行结果:

     1 --------------------try-catch-before
     2 --------------------in-finally
     3 java.io.FileNotFoundException:
     4      at java.io.FileInputStream.open0(Native Method)
     5      at java.io.FileInputStream.open(FileInputStream.java:195)
     6      at java.io.FileInputStream.<init>(FileInputStream.java:138)
     7      at java.io.FileReader.<init>(FileReader.java:72)
     8      at com.leaforbook.javaexception.App.checkedServerMethod(App.java:59)
     9      at com.leaforbook.javaexception.App.currentMethod(App.java:34)
    10      at com.leaforbook.javaexception.App.userMethod(App.java:23)
    11      at com.leaforbook.javaexception.App.main(App.java:18)

    分析:

      如果没有catch代码块,强制要求声明抛出异常。userMethod捕获到的是try代码块抛出的异常(如果finally代码块也抛出异常,这个异常就会被压抑)。finally代码块还是会执行。

    3.9 总结

    • 在运行时环境,并不会区分异常的类型,所以程序员自己要遵从良好的实践原则,否则Java异常处理机制就会被误用。
    • 然后finally代码块总是会在方法返回或方法抛出异常前执行,而try-catch-finally代码块后面的代码就有可能不会再执行。
    • finally代码块里面不推荐使用return语句或throw语句。
    • try代码块一定要有一个catch代码块或finally代码块(二者取其一就行)。
    • catch处理器的优先级比声明异常语句要高。
    • 如果多处抛出异常,finally代码块里面的异常会压抑其他异常。

    第四节 Java异常处理实践原则

    4.1 使用异常,而不使用返回码

      关于这一点,在我的译文“使用异常的优势”有很详细的描述。理解了这一点,程序员们才会想要使用Java异常处理机制。

    4.2 利用运行时异常设定方法使用规则

      很常见的例子就是,某个方法的参数不能为空。在实践中,很多程序员的处理方式是,当传入的这个参数为空的时候,就返回一个特殊值(最常见的就是返回一个null,让用户方法决定怎么办)。还有的处理方式是,自己给一个默认值去兼容这种不合法参数,自己决定怎么办。这两种实践都是不好的。

      对于第一种处理方式,返回值是用来处理正常流程的,如果用来处理异常流程,就会让用户方法的正常流程变复杂。一次调用可能不明显,当有多个连续调用就会变得很复杂了。对于第二种处理方式,看起来很强大,因为“容错”能力看起来很强,有些程序员甚至可能会为此沾沾自喜。但是它也一样让正常流程变复杂了,这不是最糟糕的,最糟糕的是,你不知道下一次用户会出什么鬼点子,传个你现有处理代码处理不了的东西进来。这样你又得加代码,继续变复杂……BUG就是这样产生的。

      好的实践方式就是,设定方法的使用规则,遇到不合法的使用方式时,立刻抛出一个运行时异常。这样既不会让主流程代码变复杂,也不会制造不必要的BUG。为什么是运行时异常而不是检查异常呢?这是为了强迫用户修改代码或者改正使用方式——这属于用户的使用错误。

    4.3 消除运行时异常

      当你的程序发生运行时异常,通常都是因为你使用别人的方法的方式不正确(如果设计这个异常的人设计错误,就另当别论。比如设计者捕获一个检查异常,然后在处理器抛出一个运行时异常给用户。如果遇上这样的供应商,还是弃用吧)。所以,一般都是采取修改代码的方式,而不是新增一个异常流程。

    4.4 正确处理检查异常

    处理检查异常的时候,处理器一定要做到下面的要求才算合格:

    • 返回到一种安全状态,并能够让用户执行一些其他的命令;或者
    • 允许用户保存所有操作的结果,并以适当的方式终止程序。

    不好的实践案例一:因为有的异常发生的概率很小,有些程序员就会写出下面的代码:

    1 public Image loadImage(String s) {
    2      try {
    3           code...
    4      } catch (Exception e)
    5      {}
    6      code2...
    7 }

    catch代码块里面什么都不写!或者只在里面打一个log。这样既不会传递到上层方法,又不会报编译错误,还不用动脑筋……

    不好的实践案例二:捕获一个检查异常,什么都不做(或只打一个log),然后抛出一个运行时异常:

    1 public Image loadImage(String s) {
    2      try {
    3           code...
    4      } catch (Exception e){
    5           throw new RuntimeExcepiton();
    6      }
    7 }

    这样也不会让上层方法感觉到这个异常的存在,也不会报编译错误了,也不用动什么脑筋……

    在案例一中,一旦出现了异常,try代码块里的代码没执行完,用户要求做的事情没做完,却又没有任何反馈或者得到一个错误反馈。

    在案例二中,一旦出现了异常,try代码块里的代码没执行完,虽然把运行时异常抛给用户了,用户也不会去处理这个异常,又没有办法通过改变使用方式消除异常,直接让用户代码崩溃掉。

    对于检查异常,好的实践方式是:

    • 让可以处理这个异常的方法去处理。衡量的标准就是在你这个方法写一个处理器,这个处理器能不能做到本节开头的那两个要求,如果不能,就往上抛。如果你不能知道所有用户的所有需求,你通常就做不到那两个要求。
    • 有必要的时候可以通过链式异常包装一下,再抛出。
    • 最终的处理器一定要做到本节开头的那两个要求。

    4.5 使主流程代码保持整洁

      一个try代码块后面可以跟多个catch代码块,这就让一些可能会发生不同异常的代码可以写在一块,让代码看起来很清晰。相反,在一个方法里写多个try-catch,或者写嵌套的try-catch,就会让主流程代码变得很混乱。

    4.6 使用try-with-resources

    请参看我的译文“try-with-resources语句”。

    try-with-resources语句比起普通的try语句,干净整洁的多。而且最终抛出的异常是正常流程中抛出的异常。

    4.7 尽量处理最具体的异常

    尽量使用最具体的异常类作为处理器匹配的类型。这样处理器就不用兼顾很多种情形,不易出错。从Java7开始,一个处理器可以处理多种异常类型。

    注意:同一个try语句中,比较具体的异常的catch代码块应写在前面,比较通用的异常的catch代码块应写在后面。

    4.8 设计自己的异常类型要遵循的原则

    当你是一个模块开发者,你就很有必要设计一组或多组自己的异常类型。一般情况下,要遵守如下原则:

    • 确定什么场景下,需要创建自己的异常类型:(参看我的译文“创建异常类”)。
    • 为你的接口方法的使用规则创建一组运行时异常。
    • 包装别人的检查异常的时候,一定也要用检查异常。这样异常才能传递给上层方法处理。
    • 设计一组有层次结构的异常,而不是设计一堆零零散散的异常。
    • 区分清楚异常发生的原因,然后决定你的异常是检查异常还是运行时异常。
    • 模块内部不需要处理自己定义的异常。

    Java异常处理机制的目的至少有三个:一是归类处理不同的异常,二是提供足够的信息方便调试,三是让主流程代码保持整洁。

  • 相关阅读:
    Asp.NET 4.0 ajax实例DataView 模板编程1
    ASP.NET 4.0 Ajax 实例DataView模板编程 DEMO 下载
    部分东北话、北京话
    .NET 培训课程解析(一)
    ASP.NET 4.0 Ajax 实例DataView模板编程2
    ASP.NET Web Game 架构设计1服务器基本结构
    ASP.NET Web Game 构架设计2数据库设计
    TFS2008 基本安装
    Linux上Oracle 11g安装步骤图解
    plsql developer远程连接oracle数据库
  • 原文地址:https://www.cnblogs.com/bsjl/p/8662660.html
Copyright © 2020-2023  润新知