欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java常见异常

程序员文章站 2022-07-15 22:31:05
...

Java异常

1.概述

发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递(throw)给某个接收者——接受者将知道如何正确处理这个问题。

C语言及其他语言的异常处理机制是通常会返回某个特殊值或者设置某个标志,并且假定接受者将对这个返回值或标志进行检查,以判断是否发生了错误。

使用异常的好处是能够降低错误处理代码的复杂度。把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。


2.基本异常

异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。

普通问题是指,在当前环境下能够得到足够的信息,总能处理这个错误。

对于异常情形,就不能继续下去,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。

当抛出异常后,有几件事会随之发生:

  1. 首先,同Java中其他对象的创建一样,将使用new在上创建异常对象
  2. 然后,当前的执行路径(它不能继续执行下去了)被终止,并且从当前环境中弹出对异常对象的引用。
  3. 异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。
  4. 这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。

异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在C和C++中,是没有办法可以强制程序在出现了问题时停止在某条路径下运行下去。异常允许我们强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。

所有的标准异常类都有两个构造器:一个默认构造器;另一个是接受字符串为参数,以便能把相关信息放入异常对象的构造器。

Throwable对象是异常类型的根类


3.捕获异常

要明白异常是如何被捕获的,必须首先理解监控区域的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。

3.1 try块

在try块里“尝试”各种(可能产生异常的)方法调用。

try{
    // code that might generate exceptions
}
  • 1
  • 2
  • 3

3.2 异常处理程序

抛出的异常必须在某处得到处理,这个地点就是异常处理程序。而且针对某个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在try块之后,以关键字catch表示:

try{
    // code that might generate exceptions
}catch(Exception1 e1){
    // handle exceptions of Exception1
}catch(Exception2 e2){
    // handle exceptions of Exception2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

每个catch子句(异常处理程序)看起来就像是接受一个且仅接受一个特殊类型的参数的方法

异常处理程序必须紧跟在try之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。

终止与恢复

异常处理理论上有两种基本模型:终止模型恢复模型

Java支持终止模型,在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行,一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。

另一种是恢复模型,是指异常处理程序的工作是修复错误,然后重新尝试调用出问题的方法,并认为第二次能成功。


4. 创建自定义的异常

不必拘泥于Java中已有的异常类型。可以通过继承异常类Exception及其子类来自定义异常类。

public class SimpleException extends Exception {
}

public class InheritingException {
    public void test() throws SimpleException{
        System.out.println("Throw SimpleException from f()");
        throw new SimpleException();
    }

    public static void main(String[] args){
        InheritingException sed = new InheritingException();
        try {
            sed.test();
        } catch (SimpleException e) {
            System.out.println("Caught it!");
            //e.printStackTrace();
        }
        System.out.println("end");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

5. 异常说明

异常说明是属于方法声明的一部分,紧跟在形式参数列表之后。

异常说明使用了附加的关键字throws,后面接一个所有潜在的异常类型的列表。例如:

public void test() throws SimpleException{
     System.out.println("Throw SimpleException from f()");
     throw new SimpleException();
}
  • 1
  • 2
  • 3
  • 4

代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有处理,编译器会发现问题,并提醒你: 
要么处理这个异常; 
要么就在异常说明中表明此方法将产生异常

通过这种自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的异常正确性。

但实际上还有这样一种情况:可以声明方法将抛出异常,实际上却不抛出。这样做的好处是异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。

这种在编译时被强制检查的异常被称为受检查的异常

受检查的含义来自于编译时是否需要检查。


6.捕获所有异常

可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类的基类Exception,就可以做到这一点。

catch(Exception e){

}
  • 1
  • 2
  • 3

这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防止它抢在其他处理程序之前先把异常捕获了。

因此Exception是与编程有关的所有异常类的基类

6.1 栈轨迹

printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一帧。元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable被创建和抛出的地方)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。

6.2 重新抛出异常

重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后序catch子句将被忽略。

如果只是把当前异常对象重新抛出,那么PrintStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的

例如:

public class Rethrowing {
    public static void test() throws Exception{
        System.out.println("original the exception in test()");
        throw new Exception("throw from test()");
    }

    public static void test1() throws Exception{
        try {
            test();
        }catch (Exception e){
            System.out.println("inside test1()");
            e.printStackTrace(System.out);
            throw e;
        }
    }

    public static void test2() throws Exception{
        try {
            test1();
        }catch (Exception e){
            System.out.println("Inside test2()");
            e.printStackTrace(System.out);
            throw (Exception)e.fillInStackTrace();
        }
    }

    public static void main(String[] args){
        try {
            test2();
        } catch (Exception e) {
            System.out.println("main:printStackTrace()");
            e.printStackTrace(System.out);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

输出结果如下:

original the exception in test()
inside test1()
java.lang.Exception: throw from test()
    at Rethrowing.test(Rethrowing.java:7)
    at Rethrowing.test1(Rethrowing.java:12)
    at Rethrowing.test2(Rethrowing.java:22)
    at Rethrowing.main(Rethrowing.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Inside test2()
java.lang.Exception: throw from test()
    at Rethrowing.test(Rethrowing.java:7)
    at Rethrowing.test1(Rethrowing.java:12)
    at Rethrowing.test2(Rethrowing.java:22)
    at Rethrowing.main(Rethrowing.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
main:printStackTrace()
java.lang.Exception: throw from test()
    at Rethrowing.test2(Rethrowing.java:26)
    at Rethrowing.main(Rethrowing.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

调用fillInStackTrace()那一行就成了异常的新发生地了

永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。他们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。

6.3 异常链

如果想要在捕获一个异常后抛出另外一个异常,并且希望把原始异常的信息保存下来,这个被称为异常链

在Throwable子类的构造器中都可以接收一个cause(因由)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置

在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器,他们是Error(用于Java虚拟机报告系统错误)、Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。


7.Java标准异常

Throwable这个Java类被用来表示任何可以作为异常被抛出的类

Throwable对象可以被分成两种类型:

  • Error : 用来表示编译时和系统错误(除特殊情况外,一般不用关心)。
  • Exception : 是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可以抛出Exception型异常。

如果对null引用进行调用,Java会自动抛出NullPointerException异常,它是运行时异常

  • RuntimeException:运行时异常(Runtime Exception)的类型很多,他们会自动Java虚拟机抛出,所以不必在异常说明中把他们列出来。这些异常都是从RuntimeException类继承而来,他们是不受检查异常。这种异常属于错误,将被自动捕获尽管通常不用捕获RuntimeException异常,但还是可以在代码中抛出RuntimeException类型的异常

如果不捕获RuntimeException这种类型的异常会发生什么事呢?

因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException类型的异常会穿越所有的执行路径直达main()方法,而不会被捕获

如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。

请务必记住:

只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误: 
1) 无法预料的错误。 
2) 作为程序员,应该在代码中进行检查的错误。


8.使用finally进行清理

对于一些代码,可能会希望无论try块中的异常是否抛出,他们都能得到执行。这通常使用于内存回收之外的情况。为了达到这个效果,可以在异常处理程序后加上finally子句。完整的异常处理程序看起来像这样:

try{
    // may throw A,B,C
}catch(A a1){

}catch(B b1){

}catch(C c1){

}finally{

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

范例:

public class FinallyWorks {
    static int count = 0;
    public static void main(String[] args){
        while (true){
            try {
                if (count++ == 0){
                    throw new SimpleException("simpleexception");
                }
                System.out.println("No exception");
            }catch (SimpleException e){
                System.out.println("SimpleException");
            }finally {
                System.out.println("In finally clause");
                if (count == 2){
                    break;
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出:

SimpleException
In finally clause
No exception
In finally clause
  • 1
  • 2
  • 3
  • 4
  • 5

无论异常是否被抛出,finally子句总能被执行

这个程序也给了我们一些思路,当Java中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把try块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。

当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或者网络连接等。

在finally类内部,从何处返回无关紧要,finally子句总是会被执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。

异常丢失

用某些特殊的方式使用finally子句,就会导致异常丢失。例如:

public class ExceptionSilencer {
    public static void main(String[] args){
        try {
            throw new RuntimeException();
        }finally {
            return;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果运行这个程序,就会看到即使抛出了异常,也不产生任何输出。


9.异常的限制

当覆盖方法时,只能抛出在基类方法的异常说明里列举的那些异常。这个限制很有用,因为这意味着,当基类使用的代码应用到其派生类对象的时候,一样能够正常工作,异常也不例外。

异常限制对构造器不起作用。基类构造器必须以这样或那样的方式被调用,派生类构造器的异常说明必须包含基类构造器的异常说明。

派生类构造器不能捕获基类构造器抛出的异常。

异常说明不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。

一个出现在基类方法中的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里。


10.构造器

在创建需要清理的对象之后,立即进入一个try-finally语句块。


11.异常匹配

抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。

查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配器基类的处理程序。


12.throw和throws有什么区别?

  1. throw用于方法内部,throws用于方法声明上。
  2. throw后跟异常对象,throws后跟异常类型。
  3. throw后只能跟一个异常对象,throws后可以一次声明多种异常类型。

    • throw语法: throw (异常对象) throw e;

    • throws语法: throws 异常类 public void test() throws Exception1,Exception2{ }


13.Java中Exception和Error有什么区别?

Exception和Error都是Throwable的子类。

  • Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕获到的,无法采取任何恢复的操作,顶多只能显示错误的信息。

  • Exception:包括RuntimeException受检查异常

    • 受检查异常也就是我们经常遇到的IO异常,以及SQL异常,都是这种异常。对于这种异常,Java编辑器强制要求我们必需对出现的这些异常进行catch

    • RuntimeException是运行时异常,当出现这样的异常时,总是由虚拟机去处理。例如NullPointerException异常,就是常见的运行时异常。

14.Java异常类介绍

Throwable类

Throwable类是所有errors和exceptions的子类。只有是Throwable类或者是Throwable的子类才能被JVM虚拟机抛出,或者是Java代码throw语句抛出。类似地,只有Throwable类或者其子类才可以在catch语句中出现

为了编译时检查异常,Throwable或者Throwable的子类,除了RuntimeException和Error都是被当作受检查的异常(checked exception).

Throwable的两个子类:Error和Exception被用来表明发生了异常情况。

Throwable包含了线程执行堆栈的快照,它同样包含一些错误的详细信息。Throwable同样可以包含一个cause:另外一个Throwable导致该Throwable被创建

cause可以关联到Throwable有两种途径:通过构造器函数,把Cause当做参数传入;通过initCause(Throwable)方法。

代码解析

  1. 构造函数
    /**
     * Constructs a new throwable with {@code null} as its detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     *
     * <p>The {@link #fillInStackTrace()} method is called to initialize
     * the stack trace data in the newly created throwable.
     */
    public Throwable() {
        fillInStackTrace();
    }

    /**
     * Fills in the execution stack trace. This method records within this
     * {@code Throwable} object information about the current state of
     * the stack frames for the current thread.
     *
     * <p>If the stack trace of this {@code Throwable} {@linkplain
     * Throwable#Throwable(String, Throwable, boolean, boolean) is not
     * writable}, calling this method has no effect.
     *
     * @return  a reference to this {@code Throwable} instance.
     * @see     java.lang.Throwable#printStackTrace()
     */
    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            // 调用native方法
            fillInStackTrace(0);
            // 空的stack
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

    private native Throwable fillInStackTrace(int dummy);

    public Throwable(String message) {
        fillInStackTrace();
        //详细的信息
        detailMessage = message;
    }

    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  1. initCause方法

初始化Throwable的cause。cause是导致该Throwable被抛出的Throwable。

该方法只能被调用一次,通常是在构造器中调用或者是在创建完Throwable之后,马上调用该方法。

使用该方法的一个范例是用来处理遗留的Throwable类型。例如:

try {
        lowLevelOp();
    } catch (LowLevelException le) {
        throw (HighLevelException)
            new HighLevelException().initCause(le); // Legacy constructor
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
    /*
     * @param  cause the cause (which is saved for later retrieval by the
     *         {@link #getCause()} method).  (A {@code null} value is
     *         permitted, and indicates that the cause is nonexistent or
     *         unknown.)
     * @return  a reference to this {@code Throwable} instance.
     * @throws IllegalArgumentException if {@code cause} is this
     *         throwable.  (A throwable cannot be its own cause.)
     * @throws IllegalStateException if this throwable was
     *         created with {@link #Throwable(Throwable)} or
     *         {@link #Throwable(String,Throwable)}, or this method has already
     *         been called on this throwable.
     * @since  1.4
     */
    public synchronized Throwable initCause(Throwable cause) {
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted", this);
        this.cause = cause;
        return this;
    }

    private Throwable cause = this;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

Exception类

Exception类是Throwable的子类,用来指示应用可能想要捕获的情况。

Exception类或者Exception的子类,除了RuntimeException,都是受检查的异常受检查的异常需要在方法或者构建器中声明

  1. 构造函数
public class Exception extends Throwable {
    /**
     * Constructs a new exception with {@code null} as its detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     */
    public Exception() {
        super();
    }

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

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Error类

Error类是Throwable类的子类,用来指示严重的错误,应用不应该来捕获它。大多数的errors是异常情况。

Error以及它的子类被当做是不受检查的异常(unchecked exception)

  1. 构造函数
public class Error extends Throwable {
    /**
     * Constructs a new error with {@code null} as its detail message.
     * The cause is not initialized, and may subsequently be initialized by a
     * call to {@link #initCause}.
     */
    public Error() {
        super();
    }

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

    public Error(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

RuntimeException类

RuntimeException是那些在JVM普通操作中抛出的异常的子类。

RuntimeException以及它的子类是不受检查异常(unchecked exception)。不受检查异常没有必要在方法或者构造器中声明,如果他们是在执行方法或者构造器时抛出,并且能在方法或者构造函数边界外传播。

编译器不会检查RuntimeException异常

  1. 构造函数
public class RuntimeException extends Exception {
    /** Constructs a new runtime exception with {@code null} as its
     * detail message.  The cause is not initialized, and may subsequently be
     * initialized by a call to {@link #initCause}.
     */
    public RuntimeException() {
        super();
    }

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

    public RuntimeException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

15.Java异常类图

Java常见异常

相关标签: 常见异常