java异常处理

版权声明:原创不易,转载请注明转自http://blog.csdn.net/weewqrer/article/details/51913194


异常恢复是提高鲁棒性最重要的方法。

前言:这篇文章是我自己的笔记,基本上是我翻译的《Thinking in Java》第12章,翻译不是目的,目的是想写一遍印象深刻,因为有些句子翻译不好,就抄了原文。。。

如果有人觉得文章中夹杂着英文很别扭的话,留言,然后我改。。。

基本概念

异常条件(exceptional conditions)是一个可以阻止程序继续执行的问题。出现了异常条件,你无法继续执行,因为在这个上下文中你没有处理这个问题的足够信息,你只能做的就是跳出当前的上下文环境,并且把问题提交给上一层。这就是抛出异常时所发生的事情。

当抛出一个异常时,会发生这么几件事情 : 首先,就像创建其它对象一样,在堆上用new创建一个异常对象;然后,当前的执行路径被终止,从当前的环境中弹出异常对象的引用;此时,异常处理机制接管过来,并且开始寻找合适的地方执行程序,而这个合适的地方就是异常处理程序(exception handler),它的工作就是从错误状态中恢复,使程序要么换一种方式运行,要么继续执行。

一个简单的例子,引用没有初始化:

if(t == null)
    throw new NullPointerException();

上面这个程序抛出了一个异常,于是在当前环境上就不必再操心了,它会在别的地方得到处理。

异常机制使得我们可以把每件事当作一个事务来处理,而异常机制可以守护这些事务,“……在分布式计算中我们需要异常机制作为最基本的保障,某项事务如果出了什么错误,可以放弃整个计算”。

我们还可以把异常看作是内建的恢复(undo)系统,因为(在细心地使用下)我们在程序中可以拥有各种不同的恢复点,如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。

异常最重要的一点是,如果程序发生了什么错误的话,异常会阻止程序沿着原来的路径走下去。这在C和C++中确实是个问题,尤其是C语言中,如果发生了错误,没办法阻止程序继续沿着原来的路走下去,所以有可能忽视这个错误很长时间从而陷入了完全错误的状态中。

异常使得我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定的状态。

异常的参数

创建异常对象有两种:带参和不带参的,不带参的在上面已经见过了,带参的看下面这个例子:

throw new new NullPointerException("t == null");

也可以从作用域(scope)中抛出异常,throw关键字抛出异常,简单的来看就是从函数或作用域(scope)中“返回”,无论是从哪里抛出异常,都意味着从这个函数或作用域退出。

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

捕获一个异常

要理解如何捕获异常,必须得先理解监控区域(guarded region)的概念,就是可能会产生异常的一段代码并且紧接着一段处理异常的代码。

try块

如果你在方法的内部,并且抛出一个异常(或者你在这个方法中调用了另一个方法,后者抛出异常),你所在的方法会在抛出异常的过程中退出。如果你不想在抛出异常的过程中退出(throw to exit)那个方法,你可以在那个方法中设置一个特殊块来捕获那个异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,所以称为try块。如下:

try{
    //code that might generate exception
}

异常处理程序 Exception handlers

当然,被抛出的异常必须在某地方得到处理。这个地方就是异常处理程序,并且针对你想捕获的每个异常,得有一个。异常处理程序紧跟try块之后,如下:

try{
    //code that might generate exception
}catch(Type1 id1){
    //处理类型Type1的异常 
}catch(Type2 id2){
    //……
}

每个catch子句就像一个接收且仅接收一个特殊类型的参数的方法,可以在处理程序的内部使用标识符(id1, id2),也可以不使用,因为异常的类型已经给出足够的信息了,但是标识符不可以省略。

异常被抛出后,异常机制会搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束。这跟switch不一样,switch语句需要在每个case后面跟一个break,以避免执行后续的case子句。注意在try块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只需要提供一个针对此类型的异常处理程序。

termination vs. resumption
终止与恢复
在异常处理理论中有两种基本的模型,java支持终止模型(java和c++都支持这种模型),这种模型假定发生的错误太严重了,已经到了没办法恢复的地步。另一种是可采取的办法是恢复,这意味着异常处理程序想做点什么来纠正错误,然后再尝试一次刚才发生异常的方法。如果你想恢复,就意味着你在异常处理后再接着执行原来的程序。

在Java中如果想恢复的话,当遇到错误时不要抛出异常,而是,调用一个可以修复这个错误的方法。或者,把try块放到while中,一直尝试直到结果满意。

从历史观点上说,编程人员一开始使用恢复的方法,但是却最终都直接跳过恢复而使用了终止模型。尽管恢复很迷人,但是在实践上并不好用,主要的原因是恢复程序得知道异常是在哪里抛出来的,并且得针对抛出地点做处理。这使得程序很难写同时也很难维护,因为大型程序有很多异常抛出点。

创建自己的异常类

必须继承一个现有的异常类,最好是在意思上跟你要创建的相近。最简单的方式就是让编译器给你创建默认的构造函数,所以基本上不用写什么代码,如下:

class SimpleException extends Exception{}

class hehe{
    public void f() throws SimpleException{
        //doSomething
        throws new SimpleException();
    }
}

当然,编译器不会为你创建带参构造函数的,实际上带参也没什么用。不过也确实可以自己写带参的,如下:

class MyException extends Exception{
    public MyException() {}
    public MyException(String msg) {super(msg);}
}

另外,捕获到异常之后,最好把它输出到标准错误流,如下:

try{
    f();
}catch(MyException e){
    e.printStackTrace();//输出到标准错误流
    e.printStackTrace(System.out);//就是输出到控制台。
}

e.printStackTrace(),this produce information about the sequence of methods that were called to get to the point where the exception happened.

异常和日志

你或许也想用java.util.logging工具把输出信息记录下来。如下:

import java.util.logging.*;
import java.io.*;

class LoggingException extends Exception {
  private static Logger logger =
    Logger.getLogger("LoggingException");
  public LoggingException() {
    StringWriter trace = new StringWriter();
    printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
}

public class LoggingExceptions {
  public static void main(String[] args) {
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
  }
}/* Output: (85% match)
Aug 30, 2005 4:02:31 PM LoggingException 
SEVERE: LoggingException
        at LoggingExceptions.main(LoggingExceptions.java:19)

Caught LoggingException
Aug 30, 2005 4:02:31 PM LoggingException 
SEVERE: LoggingException
        at LoggingExceptions.main(LoggingExceptions.java:24)

Caught LoggingException
*///:~

由上面的程序可以看出,使用时很方便,但更多的情况是使用别人的异常类,人家没有定义怎么做日志,所以还得自己在使用的时候来做。

import java.util.logging.*;
import java.io.*;

public class LoggingExceptions2 {
  private static Logger logger =
    Logger.getLogger("LoggingExceptions2");
  static void logException(Exception e) {//这里跟上面除了有参数之外,其它都一样
    StringWriter trace = new StringWriter();
    e.printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
  public static void main(String[] args) {
    try {
      throw new NullPointerException();
    } catch(NullPointerException e) {
      logException(e);
    }
  }
} /* Output: (90% match)
Aug 30, 2005 4:07:54 PM LoggingExceptions2 logException
SEVERE: java.lang.NullPointerException
        at LoggingExceptions2.main(LoggingExceptions2.java:16)
*///:~

The exception specification

你写了一个会抛出异常的方法,如何告诉调用者你的方法会抛出异常呢?如下:

void f() throws Exception{
    //……
}

这就是exception specification!如果你不写,你的方法就不会抛出异常(除了RuntimeException之外)。

Exceptions that are checked and enforced at compile time are called checked exceptions.

Catching any exception

可以用Exception来捕获所有类型的异常,如下:

catch(Exception e){
    //doSomething
}

一般把它放到catch列表的最后一个,再者,它拥有的信息不多,不过可以通过调用它的基类Throwable的方法来获得一些信息:

  • String getMessage()
  • String getLocalizedMessage()
  • String toString()

·

  • void printStackTrace() 输出到System.out
  • void printStackTrace(PrintStream) 可选的输出流
  • void printStackTrace(java.io.PrintWriter)
    Prints the Throwable and the Throwable’s call stack trace. The call stack shows the sequence of method calls that brought you to the point at which the exception was thrown.

  • Throwable fillInStackTrace()
    把栈的当前状态信息保存到Throwable对象中,当应用要重新抛出一个异常或错误的时候会用到(后面还会讲)

另外,可以从Throwable的基类Object获得一些其它的方法,比如,getClass()可以返回代表这个对象的类的对象(which returns an object representing the class of this Object),这有时对异常会有用。得到类对象(Class object)后,可以用getName()得到它的包信息,或者 getSimpleName()只得到类名。

The stack trace

The information provided by printStackTrace() can also be accessed directly using getStackTrace(). This method returns an array of stack trace elements, each representing one stack frame. Element zero is the top of the stack, and is the last method invocation in the sequence. The last element of the array and the bottom of the stack is the first method invocation in the sequence.

Rethrowing an exception

有时想重新抛出刚刚捕获的异常,尤其是当使用Exception捕获的。这很简单:

catch(Exception e){
    throw e;
}

重新抛出异常有两种方式,一种是像上面那样的,虽然是它抛出的,但是这个异常并不是它自己的,栈顶不是这个方法。另一种是下面这样的:

catch(Exception e){
    throw (Exception)e.fillInstackTrace();
}

像这样,它抛出的异常是它自己的,栈顶是这个方法。

public class Rethrowing {
  public static void f() throws Exception {
    System.out.println("originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void g() throws Exception {
    try {
      f();
    } catch(Exception e) {
      System.out.println("Inside g(),e.printStackTrace()");
      e.printStackTrace(System.out);
      throw e;
    }
  }
  public static void h() throws Exception {
    try {
      f();
    } catch(Exception e) {
      System.out.println("Inside h(),e.printStackTrace()");
      e.printStackTrace(System.out);
      throw (Exception)e.fillInStackTrace();
    }
  }
  public static void main(String[] args) {
    try {
      g();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
    try {
      h();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
  }
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:7)        //标志1 
        at Rethrowing.g(Rethrowing.java:11)
        at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:7)       //重抛出,stack不变。和标志1比较
        at Rethrowing.g(Rethrowing.java:11)
        at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()              //标志3
        at Rethrowing.f(Rethrowing.java:7)
        at Rethrowing.h(Rethrowing.java:20)
        at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()         //第二种重抛出,与标志3比较,变了
        at Rethrowing.h(Rethrowing.java:24)
        at Rethrowing.main(Rethrowing.java:35)
*///:~

调用e.fillInStackTrace()的地方成了异常的新起点!

当然,你也可以重新抛出一个其它类型的异常,但是效果就像第二种方式一样,这个地方成了异常的新起点。你所捕获的异常的原起来丢失了。

Exception chaining

当你捕获一个异常,然后抛出另一个类型的异常时,通常想保留着原来异常的信息。这叫异常链。

Throwable的三个基本异常类型子类Error,Exception,RuntimeException提供了可以传递cause参数的构造函数。如果你想用其它的类型做链,可以使用initCause()方法。cause用来作为原来的异常,通过传递它,即使重新创建一个新异常并抛出,仍然可以保留着原异常的栈跟踪(stack trace)。

下面是一个较为接近实际应用的例子:

首先说明一下这个程序要实现什么功能,DynamicFields对象包含一个Object-Object对的数组,第一个Object是一个域指示符(a String),另一个是域值,它可以是任何类型(除了没经包装的基本类型外)。(1)当创建一个DynamicFields对象时,猜测一下会用到多少个域,(2)然后当你调用setField()的时候,它先检查有没有存在,如果不存在就创建一个,然后把你的值放进去。(3)如果它没有空间了,就创建一个新的对象,新对象的长度比旧对象长1,把旧对象的内容复制过去。(4)如果试着放入一个null值,它会创建一个DynamicFieldsException异常,用initCause()传入一个NullPointerException作为cause,然后抛出DynamicFieldsException异常。(5)setField()中会调用getField(),后者有可能抛出NoSuchFieldException,此时 NoSuchFieldException被转换成RuntimeException,此刻使用了后者的带cause构造函数,如果客户调用getField(),那么客户就负责处理。


class DynamicFieldsException extends Exception {
}

public class DynamicFields {
    private Object[][] fields;

    public DynamicFields(int initialSize) {
        fields = new Object[initialSize][2];
        for (int i = 0; i < initialSize; i++)
            fields[i] = new Object[] { null, null };
    }

    public String toString() {
        StringBuilder result = new StringBuilder();
        for (Object[] obj : fields) {
            result.append(obj[0]);
            result.append(": ");
            result.append(obj[1]);
            result.append("\n");
        }
        return result.toString();
    }

    private int hasField(String id) {
        for (int i = 0; i < fields.length; i++)
            if (id.equals(fields[i][0]))
                return i;
        return -1;
    }

    private int getFieldNumber(String id) throws NoSuchFieldException {
        int fieldNum = hasField(id);
        if (fieldNum == -1)
            throw new NoSuchFieldException();
        return fieldNum;
    }

    private int makeField(String id) {
        for (int i = 0; i < fields.length; i++)
            if (fields[i][0] == null) {
                fields[i][0] = id;
                return i;
            }
        // No empty fields. Add one:
        Object[][] tmp = new Object[fields.length + 1][2];
        for (int i = 0; i < fields.length; i++)
            tmp[i] = fields[i];
        for (int i = fields.length; i < tmp.length; i++)
            tmp[i] = new Object[] { null, null };
        fields = tmp;
        // Recursive call with expanded fields:
        return makeField(id);
    }

    public Object getField(String id) throws NoSuchFieldException {
        return fields[getFieldNumber(id)][1];
    }

    public Object setField(String id, Object value) throws DynamicFieldsException {
        if (value == null) {
            // Most exceptions don't have a "cause" constructor.
            // In these cases you must use initCause(),
            // available in all Throwable subclasses.
            DynamicFieldsException dfe = new DynamicFieldsException();
            dfe.initCause(new NullPointerException());
            throw dfe;
        }
        int fieldNumber = hasField(id);
        if (fieldNumber == -1)
            fieldNumber = makeField(id);
        Object result = null;
        try {
            result = getField(id); // Get old value
        } catch (NoSuchFieldException e) {
            // Use constructor that takes "cause":
            throw new RuntimeException(e);
        }
        fields[fieldNumber][1] = value;
        return result;
    }

    public static void main(String[] args) {
        DynamicFields df = new DynamicFields(3);
        print(df);
        try {
            df.setField("d", "A value for d");
            df.setField("number", 47);
            df.setField("number2", 48);
            print(df);
            df.setField("d", "A new value for d");
            df.setField("number3", 11);
            print("df: " + df);
            // print("df.getField(\"d\") : " + df.getField("d"));
            Object field = df.setField("d", null); // Exception
            // } catch(NoSuchFieldException e) {
            // e.printStackTrace(System.out);
        } catch (DynamicFieldsException e) {
            e.printStackTrace(System.out);
        }
    }
}
null: null
null: null
null: null

d: A value for d
number: 47
number2: 48

df: d: A new value for d
number: 47
number2: 48
number3: 11

exceptions.DynamicFieldsException
    at exceptions.DynamicFields.setField(DynamicFields.java:66)
    at exceptions.DynamicFields.main(DynamicFields.java:95)
Caused by: java.lang.NullPointerException
    at exceptions.DynamicFields.setField(DynamicFields.java:67)
    ... 1 more

看Caused by:java.lang.NullPointerException……,它就是由第66行产生的。

Standard java exceptions

The Java class Throwable describes anything that can be thrown as an exception. 有两种类型的Throwble(遗传自Throwable),Error表示compile-time and System error,这些你不需要捕获,另一个是Exception是基本的类型,可以从Java标准库中或者你自己写的方法中抛出。所以编程人员关心的基本类型是Exception.

并不是所有的异常都在java.lang中,有些用来支持util, net and io,可以从它们的名字上看出来,绍java.io.IOException.

Special case: RuntimeException

本章开始处有一个简单的例子:

if(t == null)
    throw new NullPointerException();

上面的程序用于检查引用是空为空,难道我们使用的每个引用都要检查一下吗?这也太恐怖了,幸运的是,这是java标准运行时检查的一部分,Java会自己抛出一个NullPointerException。所以上面的代码是多余的。实际上有一组异常都属于这一类,它们被放到一个单一的基类中—RuntimeException.

RuntimeException及继承自它的异常是特殊的一类,写函数时抛出异常不需要指定他们。因为它们是unchecked exceptions.因为它们预示着程序有bugs,而且它们也不需要捕获,因为它们会自动处理。

看一个有趣的例子:

public class NeverCaught {
  static void f() {
    throw new RuntimeException("From f()");
  }
  static void g() {
    f();
  }
  public static void main(String[] args) {
    g();
  }
} ///:~
/*
Exception in thread "main" java.lang.RuntimeException: From f()
    at exceptions.NeverCaught.f(NeverCaught.java:7)
    at exceptions.NeverCaught.g(NeverCaught.java:10)
    at exceptions.NeverCaught.main(NeverCaught.java:13)
*/

第二行未指定抛出哪种类型的异常,第6和9行未处理异常。如果没有被捕获,当程序退出时,会自动调用printStackTrace().

Performing cleanup with finally

finally子句放在所有异常处理子句的后面。无论如何它都会被执行。

What’s finally for?

如果你想把某个东西调回原来的状态时,finally子句就很有用啦!比如一个打开的文件,或网络连接,或者屏幕上的绘图。

Using finally during return

“talk is cheap, show me your code!”
直接上程序:

    try {
      print("Point 1");
      if(i == 1) return;
      print("Point 2");
      if(i == 2) return;
      print("Point 3");
      if(i == 3) return;
      print("End");
      return;
    } finally {
      print("Performing cleanup");
    }
  }

多点退出,但也保证了清理工作的执行。此处清理工作决不包括内存清理。

pitfall: the lost exception

不幸的是,Java的异常机制中有缺陷,异常是指示你程序中的问题的东西,不应该被忽略,然而,在Java中一个异常却可能会丢失。原因是不恰当的配置finally.

如下面两个例子:

try{
    object.f();
}finally{
    object.dispose();
}

上面这种情况下,f()抛出的异常被忽略了。要避免这种情况就得在调用任何可以抛出异常的方法时用try……catch语句。

try{
    throw new RuntimeException();
}filally{
    return;
}

在应该处理异常的地方却返回了, 这是明令禁止的代码。

Exception restrictions

当重写一个方法时,只能抛出原来基类中此方法指定的异常。这是一个有用的限制,因为这意味着那些使用基类的代码仍然可以使用任何继承自基类的类(一个OOP最基本的概念),包括异常。

constructors

经常问一个问题很重要“If an exception occurs, will everything be properly cleaned up?”其实,大部分时候没有问题,但是如果有构造函数的话,有有点麻烦了。

构造函数使得对象处于一个安全的开始状态,它会做一些操作,比如打开文件—这不会被清理直到对象被用完了,或者调用了特殊的清理方法。如果在构造函数中抛出一个异常,就不会发生这种正常的清理工作了,这就意味着在写构造函数时得非常小心。

你或许认为finally是一个解决方案.But it’s not quite that simple, because finally performs the cleanup code every time. If a constructor fails partway through its execution, it might not have successfully created some part of the object that will be cleaned up in the finally clause.

下面这个例子是每次读取文件中的一行,很实用:


import java.io.*;

public class InputFile {
    private BufferedReader in;

    public InputFile(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
            // Other code that might throw exceptions
        } catch (FileNotFoundException e) {
            System.out.println("Could not open " + fname);
            // Wasn't open, so don't close it
            throw e;
        } catch (Exception e) {
            // All other exceptions must close it
            try {
                in.close();
            } catch (IOException e2) {
                System.out.println("in.close() unsuccessful");
            }
            throw e; // Rethrow
        } finally {
            // Don't close it here!!!
        }
    }

    public String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException("readLine() failed");
        }
        return s;
    }

    public void dispose() {
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch (IOException e2) {
            throw new RuntimeException("in.close() failed");
        }
    }
}

解析:
9行:FileReader一般不怎么用,除非用来创建BufferedReader,如果前者的构造函数不成功会抛出FileNotFoundException异常,此时对象还没有建成,肯定无法关闭。
15行:此时得关闭了,但是关闭的时候也有可能抛出异常。
23行:此时不可以关闭文件,因为有可能没有打开。

33行:捕获IOException,抛出RuntimeException.此时的问题是是否在这一层上处理问题,还是只是把异常重抛出去。

38行:每当文件用过了之后都要关闭。

最安全地使用一个带有构造函数且事后需要清理的类的方法是使用嵌入try块,如下:

try {
    InputFile in = new InputFile("Cleanup.java");
    try {
        String s;
        int i = 1;
        while ((s = in.getLine()) != null)
            ; // Perform line-by-line processing here...
    } catch (Exception e) {
        System.out.println("Caught Exception in main");
        e.printStackTrace(System.out);
    } finally {
        in.dispose();
    }
} catch (Exception e) {
    System.out.println("InputFile construction failed");
}

仔细看这里的逻辑,InputFile对象的构造在它自己的try块中,如果构造出了问题,程序会进入最外面的catch子句,其中不会调用dispose()。然而,如果构建成功了,那么就必须确保调用dispose()了,所以在构造之后紧接着写try子句,此时这个内部的try子句就可以有finally啦,并且在其中调用dispose().这就能保证创建失败不调用关闭,创建成功才调用关闭。

即使构造函数不抛出异常,也应该这种普遍的清理方法,就是在构造之后紧跟着try……finally子句!

NeedsCleanup nc1 = new NeedsCleanup();
try{
    //
}finally{
    nc2.dispose();
}

Exception matching

当抛出异常后,异常处理系统会检查最近的异常处理程序,如果匹配就进入处理程序了,不会再搜索其它的了。

并且基类的异常处理程序会捕获其子类异常。

Alternative approaches

异常处理机制是为也简化异常处理代码量的,多个可能抛出异常的调用可以使用一个异常处理程序,而且这个机制使得编程人员关注问题的处理,而不是总被要异常打断。

对于 checked exception,编译器强写try..catch,于是大部分人就这么做了:

try{
    //
}catch(IOException e){
    //空着呢,后来就忘记了,使得异常丢失。应该加上printStackTrace(System.out).
}

好多经验丰富的编程人员说,Java的checked excpetion没有好处。

除了被强制写了try…catch后不知道怎么处理之外,还有可选的方案,如下:

passing exceptions to the console

这个方法对于简单的程序可行。

public static void main(String[] args) throws Exception{
    //一些可能抛出异常的代码
}

throws Exception就使你免去写try…chatch子句了。

Converting checked to unchecked exception

上面的方法只适合写小程序,不实用。

当写实用的方法时,你调用了一个会抛出异常的方法,但是不知道怎么处理这个异常,而又不想只打印出信息,此时可以使用这个方法:捉住这个抛出那个!

try{

}catch(IDontKnowWhatToDoWithThisCheckedException e){
    throw new RuntimeException(e);
}

原理在前面已经讲过了,给RuntimeException的构造函数传递一个cause.这个方法不会丢失信息,使得你免去写异常处理程序了,而且还不用指定异常类型。

这样你写出来的方法,在别人调用你的方法时需要写try…catch就可以调用。被包装起来的异常就变成了RuntimeException的cause了,而且在想处理的时候用getCause()。


class WrapCheckedException {
    void throwRuntimeException(int type) {
        try {
            switch (type) {
            case 0:
                throw new FileNotFoundException();
            case 1:
                throw new IOException();
            case 2:
                throw new RuntimeException("Where am I?");
            default:
                return;
            }
        } catch (Exception e) { // Adapt to unchecked:
            throw new RuntimeException(e);
        }
    }
}

class SomeOtherException extends Exception {
}

public class TurnOffChecking {
    public static void main(String[] args) {
        WrapCheckedException wce = new WrapCheckedException();
        // You can call throwRuntimeException() without a try
        // block, and let RuntimeExceptions leave the method:
        wce.throwRuntimeException(3);
        // Or you can choose to catch exceptions:
        for (int i = 0; i < 4; i++)
            try {
                if (i < 3)
                    //调用时不用try...catch语句
                    wce.throwRuntimeException(i);
                else
                    throw new SomeOtherException();
            } catch (SomeOtherException e) {
                print("SomeOtherException: " + e);
            } catch (RuntimeException re) {
                try {
                    throw re.getCause();
                } catch (FileNotFoundException e) {
                    print("FileNotFoundException: " + e);
                } catch (IOException e) {
                    print("IOException: " + e);
                } catch (Throwable e) {
                    print("Throwable: " + e);
                }
            }
    }
}

39行:捕获后在catch子句中,用getCause()再抛起来,写各个catch就捕获,单独针对地处理。

参考书:《Thinking in java》英文版 第4版

你可能感兴趣的:(java)