Java异常处理

282 阅读8分钟

异常 这个词有 我对此感到意外 的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。

使用异常带来的另一个相当明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的 异常处理程序 中。这种方式不仅节省代码,而且把 描述在正常执行过程中做什么事 的代码和 出了问题怎么办 的代码相分离。

基本异常

异常情形 是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在 当前环境下 无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。

当抛出异常后,有几件事会随之发生。首先,同Java中的其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序。它的任务是将程序从错误状态中回复,以使程序能要么换一种方式运行,要么继续运行下去。

异常参数

与使用Java中的其他对象一样,我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接收字符串作为参数,以便能把相关信息放入异常对象的构造器:

throw new NullPointerException("t = null");

关键字 throw 将产生许多有趣的结果。在使用new创建了异常对象之后,此对象的引用将传给throw。

此外,能够抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)

捕获异常

如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。因为在这个块里 尝试 各种方法调用,所以称为try块。它是跟在try关键字之后的普通程序块:

 try {
     // Code that might generate exception
}

有了异常处理机制,可以把所有动作都放在try块,然后只需在一个地方就可以捕获所有的异常。

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

  try {
            // Code that might generate exception
        }catch (Type1 id1){
            //Handle exception of Type1
        }catch (Type2 id2){
            //Handle exception of Type2
        }catch (Type3 id3){
            //Handle exception of Type3
        }

创建自定义异常

不必拘泥于Java中已有的异常类型。Java提供的异常体系不可能遇见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。

要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常类不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,这是因为,对异常来说,最重要的部分就是类名,所以默认构造函数的异常类在大多数情况下已经够用了。所以这几乎不用写多少代码:

public class SimpleException extends Exception {}

public class InheritingExceptions {
    public void f() throws SimpleException {
        System.out.println("Throw SimpleException form f()");
        throw new SimpleException();
    }
}

public class Main {

    public static void main(String[] args) {
        InheritingExceptions sed = new InheritingExceptions();
        try {
            sed.f();
        } catch (SimpleException e) {
            System.out.println("Caught it!");
            //            e.printStackTrace();
        }

    }
}

异常说明

Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找throw语句来获知相关信息,然而程序库通常并与不与源代码一起发布。

为了防止这样的问题,Java提供了相应的语法,使你能以礼貌的方式告诉客户端程序员某个方法一定会抛出的异常类型,然后客户端程序员就可以应用相应的处理。这就是 异常说明

异常说明 使用了附加的关键字 throws, 后面接一个所有潜在异常类型的列表,所以方法定义可能看起来像这样:

public void g() throws SimpleException, IOException {}

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

可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是:为异常先占个位子,以后可以抛出这种异常而不用修改已有的代码。在定义抽象类基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。

使用finally进行清理

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

对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try快里发生什么,内存总能得到释放。但Java有垃圾回收机制,所以内存不再是问题。而且,Java也没有析构函数可供调用。所以,Java就是要把除内存之外的资源恢复到他们的初始状态时,要用到finally子句。这种需要清理的资源资源包括但不限于:已经打开的文件或网络连接等等。

Java中try catch finally 语句中含有return语句的执行顺序?

try语句在返回前,将其他所有的操作执行完,保留好要返回的值,然后转入执行finally中的语句,而后分为以下三种情况

  1. 如果 finally 中有 return 语句,则会将try中的return语句 覆盖 掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
  2. 如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
  3. 如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
    1. 如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
    2. 如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。