Java-异常-知识点整理

  Java异常指在程序运行时可能出现的一些错误,比如试图打开一个根本不存在的文件等,异常处理将会改变程序的控制流程,让程序有机会对错误做出处理。Java通过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的错误条件,当条件生成时,错误将引发异常。简单概括:程序出现不正常情况后,程序将会跳出当前环境,并且抛出异常。

Java异常架构

Throwable

Java所有异常类都是 Throwable的子类。它包括Java异常处理的两个重要子类:Error和Exception。Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace( ) 等接口用于获取堆栈跟踪数据等信息。

Error(错误)

Error 及其子类来描述Java运行系统中的内部错误以及资源耗尽的错误。这是程序无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时 JVM 出现问题,通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)、OutOfMemoryError(内存不足错误)、StackOverflowError(栈溢出错误)。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。因此,当此类错误发生时,应用程序不应该去处理此类错误。

Exception(异常)

Java 的所有异常可以分为可查异常(checked exception)和非可查异常(unchecked exception)

  • 可查异常
      编译器要求必须处理的异常。正确的程序在运行过程中,这类异常的发生在一定程度上是可以预计的。一旦发生此类异常,就必须采用某种方式进行处理。**除 RuntimeException 及其子类外,其他的 Exception 异常都属于可查异常。**编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

  • 非可查异常
      编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。

Exception 异常又可分为两大类:运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

  • 运行时异常
      RuntimeException类及其子类异常,表示 JVM 在运行期间可能出现的异常。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。Java编译器不会检查它,也就是说,当程序中出现这类异常时,也会编译通过。
      RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获 就算我们没写异常捕获语句运行时也会抛出错误 ,此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。

  • 非运行时异常 (编译异常)
      是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。

Java异常关键字

  • try – 用于监听,将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw – 用于抛出一个异常,抛出的时候是抛出的是一个异常类的实例化对象,在异常处理中,try 语句要捕获的是一个异常对象,那么此异常对象也可以自己抛出。
  • throws – 用在方法签名中,用于声明该方法可能抛出的异常,使用 throws 抛出的运行时异常相当于提示调用者,该方法有风险,至于是否处理该风险,由调用者决定。

示例一:try-catch-finally

     public static void main(String[] args) { 

        MyException myException = new MyException();

        try{ 

            myException.myMethod();

        }catch (ClassNotFoundException exception){ 

            exception.printStackTrace();

        }finally { 

            System.out.println("finaly一定执行");

        }
    }
    //定义一个方法
    public void myMethod(){ 

        System.out.println("方法执行!!!");

    }

示例二:throw

     /** * 只要java抛出异常 程序就会被终止 * @param args */
    public static void main(String[] args) { 

        System.out.println("hello world!!!");

        //创建一个异常对象
        NullPointerException nullPointerException = new NullPointerException();

        System.out.println("hello java!!!");

        //抛出异常
        throw nullPointerException;

        //该语句无法正确运行
        System.out.println("hello world!!!");

    }

示例三:throws

     //定义一个方法
    public void myMethod() throws ClassNotFoundException{ 
    
        Class.forName("com.sun.moxuyou");   //莫须有的Class
        
    }

Java异常处理流程

  在了解Java异常处理流程之前,请先看一道题目,猜测一下运行结果。

public class TestException { 

    public TestException() { 
    }

    boolean testEx() throws Exception { 
        boolean ret = true;
        try { 
            ret = testEx1();
        } catch (Exception e) { 
            System.out.println("testEx, catch exception");
            ret = false;
            throw e;
        } finally { 
            System.out.println("testEx, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx1() throws Exception { 
        boolean ret = true;
        try { 
            ret = testEx2();
            if (!ret) { 
                return false;
            }
            System.out.println("testEx1, at the end of try");
            return ret;
        } catch (Exception e) { 
            System.out.println("testEx1, catch exception");
            ret = false;
            throw e;
        } finally { 
            System.out.println("testEx1, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx2() throws Exception { 
        boolean ret = true;
        try { 
            int b = 12;
            int c;
            for (int i = 2; i >= -2; i--) { 
                c = b / i;
                System.out.println("i=" + i);
            }
            return true;
        } catch (Exception e) { 
            System.out.println("testEx2, catch exception");
            ret = false;
            throw e;
        } finally { 
            System.out.println("testEx2, finally; return value=" + ret);
            return ret;
        }
    }

    public static void main(String[] args) { 
        TestException testException1 = new TestException();
        try { 
            testException1.testEx();
        } catch (Exception e) { 
            e.printStackTrace();
        }
    }
}

  如果你的答案是下面这样的,那对于try-catch-finally,你仍需要加强学习。

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false

正确答案

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

  在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。

  抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

  捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

  对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同。

  由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。

  对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。

  对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。

  能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。

  任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。

  从方法中抛出的任何异常都必须使用throws子句。

  捕捉异常通过try-catch语句或者try-catch-finally语句实现。

   总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。

Java异常常见面试题

  1. Error 和 Exception 区别是什么?
      Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
      Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

  2. 运行时异常和一般异常(受检异常)区别是什么?
      运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
      受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
      RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

  3. JVM 是如何处理异常的?
      在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
      JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。

  4. throw 和 throws 的区别是什么?
      Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。

throws 关键字和 throw 关键字在使用上的几点区别如下:

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
  1. final、finally、finalize 有什么区别?
      final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
    finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
      finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

  2. NoClassDefFoundError 和 ClassNotFoundException 区别?
      NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
      引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
      ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。

  3. try-catch-finally 中哪个部分可以省略?
    答:catch 可以省略
      原因:
      更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
      理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

  4. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
    答:会执行,在 return 前执行。
      注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

本文地址:https://blog.csdn.net/MQNing_Star/article/details/112907309