zoukankan      html  css  js  c++  java
  • Java 基础 Exception和Error

     

    综述

    Exception 和 Error 都是继承了 Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。从设计初衷也能看出区别:Java希望可以从异常中恢复程序, 但却不应该尝试从错误中恢复程序:

    • Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。
    • Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。Exception 又分为检查型异常(checked exception)非检查型异常(unchecked exception、runtime exception):
      • 检查型异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。
      • 非检查型异常就是所谓的运行时异常(runtime exception),类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。RuntimeException是非常特殊的子类,可以不用throw和throws。哪怕你throw了,也没必要throws; 即使你throws了,调用者也没必要try-catch。还是因为runtime exception 还是希望能暴露出来,而不是由程序swallow或者自行处理。

    常见的检查型异常(checked exception)非检查型异常(unchecked exception、runtime exception)

    常见的五个checkedException编译时异常:

    • SQLException 
    • IOexception
    • FileNotFoundException
    • ClassNotFoundException
    • EOFException 

    常见的五个RunTimeException异常

    • NullPointException
    • StringIndexOutOfBoundsException
    • ArrayIndexOutOfBoundsException 
    • IllegaArguementException
    • ArithmeticException 

    形象比喻,快速理解

    1.假如你开车上山,车坏了,你拿出工具箱修一修,修好继续上路(Exception被捕获,从异常中恢复,继续程序的运行)

    2.车坏了,你不知道怎么修,打电话告诉修车行,告诉你是什么问题,要车行过来修。(在当前的逻辑背景下,你不知道是怎么样的处理逻辑,把异常抛出去到更高的业务层来处理)。

    3.你打电话的时候,要尽量具体,不能只说我车动不了了。那修车行很难定位你的问题。(要捕获特定的异常,不能捕获类似Exception的通用异常)。

    4.还有一种情况是,你开车上山,山塌了,这你还能修吗?(Error:导致你的运行环境进入不正常的状态,很难恢复)

    操作Throwable的元素/关键字

    Throw VS Throws

    不同点

    throw:

    • 表示方法内抛出某种异常对象
    • 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出 即需要加上 throws 语句 (看例子:https://blog.csdn.net/meism5/article/details/90414147)或者 在方法体内 try catch 处理该异常,否则编译报错
    • 执行到 throw 语句则后面的语句块不再执行

    throws:

    • 方法的定义上使用 throws 表示这个方法可能抛出某种异常
    • 需要由方法的调用者进行异常处理

    相同点

    两者都是延迟处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数本身去处理异常,真正的处理异常由函数的上层调用者处理。(这里的延迟并不一定不好,可能函数的调用者更清楚业务逻辑,对该异常能做出更合适的处理方式)

    Throw  

    throw是语句抛出一个异常,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常:

    public static void main(String[] args) { 
        String s = "abc"; 
        if(s.equals("abc")) { 
          throw new NumberFormatException(); 
        } else { 
          System.out.println(s); 
        } 
        //function(); 
    }

    运行时,系统会抛出异常:

    Exception in thread "main" java.lang.NumberFormatException at......

    Throws

    throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)

    public void function() throws Exception{......}

    当某个方法可能会抛出某种异常时用于throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理.

    public class testThrows {
     
        public static void function() throws NumberFormatException {
            String s = "abc";
            System.out.println(Double.parseDouble(s));
        }
     
        public static void main(String[] args) {
            try {
                function();
            } catch (NumberFormatException e) {
                System.err.println("非数据类型不能强制类型转换。");
                //e.printStackTrace(); 
            }
        }
    }

    运行结果:

    非数据类型不能强制类型转换。

    try catch finally 与 continue, break, return, throw 的执行关系

    详细请看:https://www.cnblogs.com/bethunebtj/p/4676020.html 

    情景1 - finally块不会执行的情景

    1-如果程序在try块之前就抛出了异常,finally块不会执行。

    2-如果try块中调用了 System.exit(0),终止了 Java 虚拟机的运行,那么finally块不会执行。(不常见的情况)

    情景2 - finally块会执行的情景,要点总结

    1-在排除了以上 finally 语句块不执行的情况后,finally 语句块就一定会执行

     2-finally 语句块是在 try 或者 catch 中的 return 语句之前执行的,更加一般的说法是,finally 语句块应该是在控制转移语句之前执行。(控制转移语句: return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。)因此注意:不要在finally代码块中处理返回值。

    3-请看下面两个例题:(详细解释请看: https://www.cnblogs.com/bethunebtj/p/4676020.html )

    清单 5.

    public class Test { 
     public static void main(String[] args) { 
            System.out.println("return value of getValue(): " + getValue()); 
         } 
    
     public static int getValue() { 
            try { 
                     return 0; 
            } finally { 
                     return 1; 
                } 
         } 
     }

    清单 5 的执行结果:

     return value of getValue(): 1

    清单 6.

    public class Test { 
     public static void main(String[] args) { 
            System.out.println("return value of getValue(): " + getValue()); 
         } 
    
     public static int getValue() { 
            int i = 1; 
            try { 
                     return i; 
            } finally { 
                     i++; 
            } 
         } 
     }

    清单 6.执行结果:

    return value of getValue(): 1

    利用我们上面分析得出的结论:finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。 由此,可以轻松的理解清单 5 的执行结果是 1。因为 finally 中的 return 1;语句要在 try 中的 return 0;语句之前执行,那么 finally 中的 return 1;语句执行后,把程序的控制权转交给了它的调用者 main()函数,并且返回值为 1。那为什么清单 6 的返回值不是 2,而是 1 呢?按照清单 5 的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i;之前执行啊? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

    关于 Java 虚拟机是如何编译 finally 语句块的问题,有兴趣的读者可以参考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 节 Compiling finally。那里详细介绍了 Java 虚拟机是如何编译 finally 语句块。实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

    由上图,我们可以清晰的看出,在 finally 语句块(iinc 0, 1)执行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成这个任务的指令是 istore_1;然后执行 finally 语句块(iinc 0, 1),finally 语句块把位于 0 这个位置的本地变量表中的值加 1,变成 2;待 finally 语句块执行完毕之后,把本地表量表中 1 的位置上值恢复到操作数栈(iload_1),最后执行 ireturn 指令把当前操作数栈中的值(1)返回给其调用者(main)。这就是为什么清单 6 的执行结果是 1,而不是 2 的原因。

    常见的Exception和Error的子类

     

    NoClassDefFoundError VS ClassNotFoundException 

    ClassNotFoundException

    NoClassDefFoundError

    It is an exception. It is of type java.lang.Exception.

    It is an error. It is of type java.lang.Error.

    It occurs when an application tries to load a class at run time which is not updated in the classpath.

    It occurs when java runtime system doesn’t find a class definition, which is present at compile time, but missing at run time.

    It is thrown by the application itself. It is thrown by the methods like Class.forName(), loadClass() and findSystemClass().

    It is thrown by the Java Runtime System.

    It occurs when classpath is not updated with required JAR files.

    It occurs when required class definition is missing at runtime.

    ClassNotFoundException

     ClassNotFoundException is a runtime exception that is thrown when an application tries to load a class at runtime using the Class.forName() or loadClass() or findSystemClass() methods ,and the class with specified name are not found in the classpath. For example, you may have come across this exception when you try to connect to MySQL or Oracle databases and you have not updated the classpath with required JAR files. Most of the time, this exception occurs when you try to run an application without updating the classpath with required JAR files.

    For example, the below program will throw ClassNotFoundException if the mentioned class “oracle.jdbc.driver.OracleDriver” is not found in the classpath.

    public class MainClass
    {
        public static void main(String[] args)
        {
            try
            {
                Class.forName("oracle.jdbc.driver.OracleDriver");
            }catch (ClassNotFoundException e)
            {
                e.printStackTrace();
            }
        }
    }

    If you run the above program without updating the classpath with required JAR files, you will get an exception akin to:

    java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Unknown Source)
    at pack1.MainClass.main(MainClass.java:17)

    NoClassDefFoundError

    NoClassDefFoundError is an error that is thrown when the Java Runtime System tries to load the definition of a class, and that class definition is no longer available. The required class definition was present at compile time, but it was missing at runtime. For example, compile the program below.

    class A
    {
      // some code
    }
    public class B
    {
        public static void main(String[] args)
        {
            A a = new A();
        }
    }

    When you compile the above program, two .class files will be generated. One is A.class and another one is B.class. If you remove the A.class file and run the B.class file, Java Runtime System will throw NoClassDefFoundError like below:

    Exception in thread "main" java.lang.NoClassDefFoundError: A
    at MainClass.main(MainClass.java:10)
    Caused by: java.lang.ClassNotFoundException: A
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

    Exception的最佳实践

    Reference: https://vitalflux.com/java-top-5-exception-handling-coding-practices-avoid/

    不要捕获Throwable

     因为Throwable是Exception 和 Error的父类,会连同Error (例如OutOfMemoryError, StackOverFlowError 和 InternalError) 也被捕获。Error在设计之初就是不应该被捕获的, 因为它是那种不可修复的错误。

    不要抛出Generic exceptions (Error, RuntimeException, Throwable and Exception)

    如果throw generic exceptions, 会导致类和函数无法捕获到预期的异常。导致可能延迟很久才捕获到异常,增大了修复和debug的难度。

    不要调用printStackTrace()函数

    来打印错误信息。Following are some of the reasons why one should avoid invoking printStackTrace method on Throwable/Exception classes and instead use Logger method using one of the frameworks such as LogBack or Log4J:

    • Difficult to Retrieve Logs for Debugging:The logs written using printStackTrace is written to System.err which is hard to route or filter elsewhere. Instead, using Loggers, it is easy to retrieve logs for debugging purpose.
    • Violation of Coding Best Practices:Generally, as per coding guidelines in production-ready applications, developers need to use Logger methods for logging different level of information. However, when it comes to exception handling, the instances of printStackTrace are commonly found in various places. This is, thus, a violation of coding practice and, thus, should be avoided.
    • Following are some good pages explaining the reasons in detail:

    尽量不要捕获类似 Exception 这样的通用异常

    而是应该捕获特定异常。在这里是 Thread.sleep() 抛出的 InterruptedException。

    try {
      // 业务代码
      //
      Thread.sleep(1000L);
    } catch (Exception e) {
      // Ignore it
    }

    不要生吞(swallow)异常

    这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。

    异常处理器应保留原始异常完整信息

    When writing code for doing exception handling, I have often seen code such as following which does some of the following:

    In the code sample below, the exception is lost.

    try { 
        /* ... */ 
    } catch( Exception e ) {
        SomeLogger.info( e.getMessage() ); // The exception is lost. Just that exception message is written; Also, context information is not logged.
    }

    In the code sample below, whole exception object is lost.

    try { 
        /* ... */ 
    } catch( Exception e ) {
        SomeLogger.info( "some context message" ); // The exception is lost
    }

    In the code sample below, no context message is provided.

    try { 
        /* ... */ 
    } catch( Exception e ) {
            SomeLogger.info( e ); // No context message
    }

    As a best practice, one would want to do something like following:

    try { 
      /* ... */ 
    } catch( Exception e ) {
      SomeLogger.info( "some context message", e ); // Context message is there. Also, exception object is present
    }

     In case of throwable exceptions, following should be done:

    try {
      /* ... */
    } catch (Exception e) {                                                   
      throw new CustomRuntimeException("context", e); // Context message is there. Also, exception object is present
    }

     

    自定义Exception

    Demo

    前面所讲的异常,都是系统自带的,系统自己处理,但是很多时候项目会出现特有问题,而这些问题并未被java所描述并封装成对象,所以对于这些特有的问题可以按照java的对问题封装的思想,将特有的问题进行自定义异常封装。在Java中要想创建自定义异常,需要继承Throwable或者他的子类Exception。

    语法

    class  自定义异常类 extends 异常类型(Exception){
    
     // 因为父类已经把异常信息的操作都完成了,所在子类只要在构造时,将异常信息传递给父类通过super 语句即可。
      // 重写 有参 和 无参  构造方法
    }

    例如:

    // 备注: 这些方法怎么来的? 重写父类Exception的方法
    public class CustomException extends Exception {
    
        //无参构造方法
        public CustomException(){
    
            super();
        }
    
        //有参的构造方法
        public CustomException(String message){
            super(message);
    
        }
    
        // 用指定的详细信息和原因构造一个新的异常
        public CustomException(String message, Throwable cause){
    
            super(message,cause);
        }
    
        //用指定原因构造一个新的异常
         public CustomException(Throwable cause) {
    
             super(cause);
         }
    
    }

     如何定义checked异常和unchecked异常

     When defining your own exception type, study the existing exception classes in the Java API and try to extend a related exception class. For example, if you’re creating a new class to represent when a method attempts a division by zero, you might extend classArithmeticException because division by zero occurs during arithmetic.

    If the existing classes are not appropriate superclasses for your new exception class, decide whether your new class should be a checked or an unchecked exception class.

    • If clients should berequired to handle the exception, the new exception class should be a checked exception (i.e., extend Exception but notRuntimeException). The client application should be able to reasonably recover from such an exception.
    • If the client code should be able to ignore the exception (i.e., the exception is an unchecked one), the new exception class should extend RuntimeException.

     

  • 相关阅读:
    The test form is only available for requests from the local machine
    64位Win7下,先安装Visual Studio,后安装IIS的设置步骤
    [转] 如何在 64 位的 Windows 7 中安裝 PLSQL DEVELOPER 8 和 Oracle 11g x64 Client
    excel对csv的转义
    js中没有引用的匿名函数调用方法
    缓存实现条件
    js对象成员的删除特性 (delete)
    js语法作用域之间的相关性
    【转】UBOOT之四:uboot.lds分析
    linux C 中的volatile使用
  • 原文地址:https://www.cnblogs.com/frankcui/p/10900958.html
Copyright © 2011-2022 走看看