Throwable
能够抛出任意类型的Throwable对象,它是异常类型的根类。
![](https://i-blog.csdnimg.cn/blog_migrate/376230f812cd1119d22cc9b3a7205ee3.png)
Error
(1)总是不可控制的(unchecked);
(2)用于表示编译时和系统错误;
(3)如何可能的话,应该在系统级被捕捉。
Exception
(1)可以是可被控制(checked) 或不可控制的(unchecked);
(2)表示一个由程序员导致的错误;
(3)应该在应用程序级被处理。
RuntimeException类及子类均为unchecked,该异常类与Error都不建议try/catch。常见的RuntimeException:NullPointerException、ClassCastException、IllegalArgumentException、ArithmeticException、ArrayStoreException 、IndexOutOfBoundsException、NegativeArraySizeException、NumberFormatException、SecurityException、UnsupportedOperationException。
非RuntimeException为checked,必须要么try,要么throw。
异常处理理论上有两种基本模型:终止模型和恢复模型。
异常信息
(1)通常异常对象中仅有的信息就是异常类型。
class MyException extends Exception {
MyException() { super(); }
MyException(String msg) { super(msg); }
}
public class Test {
public static void main(String[] args) {
try {
throw new MyException("My Exception");
} catch (MyException e) {
System.out.println("Caught Exception");
System.out.println("getMessage(): " + e.getMessage());
System.out.println("getLocalizedMessage(): " + e.getLocalizedMessage());
System.out.println("toString(): " + e.toString());
System.out.println("printStackTrace(): ");
e.printStackTrace(System.out);
}
}
} /* Output:
Caught Exception
getMessage(): My Exception
getLocalizedMessage(): My Exception
toString(): MyException: My Exception
printStackTrace():
MyException: My Exception
at Test.main(Test.java:8)
*///:~
(2)e.printStackTrace()会将信息输出到标准错误流;e.printStackTrace(PrintWriter out)则是输出至指定的输出流。通常的做法是使用标准错误流,因为System.out也许会被重定向。
另外,补充System.out/System.in/System.err的重定向。从JDK的源码可以看到这三个流都是final static类型。也就是用户无法通过更改引用的方式来进行重定向,必须借助setIn(InputStream in)/setOut(PrintStream out)/setErr(PrintStream err)。
/**
* The "standard" input stream. This stream is already
* open and ready to supply input data. Typically this stream
* corresponds to keyboard input or another input source specified by
* the host environment or user.
*/
public final static InputStream in = null;
/**
* The "standard" output stream. This stream is already
* open and ready to accept output data. Typically this stream
* corresponds to display output or another output destination
* specified by the host environment or user.
*/
public final static PrintStream out = null;
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
(3)记录异常日志可使用java.util.logging工具将输出记录到日志中。
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
class ExceptionLog {
private static Logger logger = Logger.getLogger("ExceptionLog");
public static void logException(Exception e) {
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class Test {
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException e) {
ExceptionLog.logException(e);
}
}
}
(4)最后,补充一点:在catch中可能需要重新抛出异常。使用throw e的问题在于e.printStackTrace()会保留原始的异常抛出点的栈信息,如需重新更新这个栈信息可使用throw (Exception)e.fillInStackTrace()。
异常链
通常想要在捕获一个异常后抛出另一个异常,并希望把原始异常的信息保存下来,这就被称为异常链。
将Throwable对象作为cause参数是用来保留原始异常信息的。Error、Exception、RuntimeException类提供有cause参数的构造器。但大多数异常都没有这个构造函数,应该使用e.initCause(cause)。
try/catch/finally
(1)在代码块中,throw/return语句后不要再写其他语句,否则会因为执行不到而导致编译失败。
(2)一般声明几个异常就应该对应几个catch。当出现多个catch时应该将处理基类异常的catch子句放在其子类的最下面,否则因为下面的catch语句永远得不到执行而导致编译失败。另外,catch中的unchecked异常必须是try语句块中已声明的异常,否则会编译失败。
class E1 extends Exception {}
class E2 extends E1 {}
class E3 extends E2 {}
class A {
void f() throws E1{
throw new E1();
}
}
class B extends A {
void f() throws E2{
throw new E2();
}
}
class C extends B {
void f() throws E3{
throw new E3();
}
}
public class Test {
public static void main(String[] args) {
A a = new C();
try {
a.f();
} catch (E3 e) {
e.printStackTrace();
} catch (E2 e) {
e.printStackTrace();
} catch (E1 e) {
e.printStackTrace();
}
}
} /* Output:
E3
at C.f(Test.java:16)
at Test.main(Test.java:23)
*///:~
(3)finally为必定会执行的部分,只有一种情况不会执行那就是System.exit(0)。将除内存外的资源恢复到它们的初始状态,就要用到finally子句。
(4)finally子句中最好不要使用return语句。下面这段程序结果返回2,程序会先执行try语句中的a++,在即将return时转而执行finally的return语句退出函数。另外,在finally的return处会产生警告。
public class Test {
@SuppressWarnings({ "finally" })
static int test() {
int a = 0;
try {
return a++;
} finally {
return ++a;
}
}
public static void main(String[] args) {
System.out.println(test());
}
}
(5)某些特殊方式使用finally子句会造成异常丢失。
class E1 extends Exception {
E1() {}
E1(String msg) { super(msg); }
}
class E2 extends Exception {
E2() {};
E2(String msg) { super(msg); }
}
public class Test {
static void fun1() throws E1 {
throw new E1("fun1()抛出的异常");
}
static void fun2() throws E2 {
throw new E2("fun2()抛出的异常");
}
public static void main(String[] args) {
try {
try {
fun1();
} finally {
fun2();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} /* Output:
E2: fun2()抛出的异常
at Test.fun2(Test.java:14)
at Test.main(Test.java:21)
*///:~
public class Test {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
return;
}
}
}///:~
异常的限制
当覆盖方法时,只能抛出在基类方法的异常说明列表中列出的异常(或这些异常的子类)。另外,子类向上提升后,必须捕捉基类方法中的异常。
异常限制对构造器不起作用,但子类构造器的异常说明必须包含基类构造器的异常说明,且子类构造器中无法捕获基类构造器抛出的异常。
class BaseballException extends Exception{}
class Foul extends BaseballException{}
class Strike extends BaseballException{}
abstract class Inning {
public Inning() throws BaseballException{}
public void walk(){}
public void event() throws BaseballException{}
public abstract void atBat() throws Strike,Foul;
}
class StormException extends Exception{}
class RainOut extends StormException{}
class PopFoul extends Foul{}
interface Storm {
public void event() throws RainOut;
public void rainHard() throws RainOut;
}
class StormyInning extends Inning implements Storm {
//必须抛BaseballException
StormyInning()throws BaseballException{}
//必须抛BaseballException
StormyInning(String s) throws RainOut, BaseballException{}
//只能抛RainOut类或其子类
public void rainHard() {}
//不能抛任何异常
public void event() {}
//不能抛任何异常
public void walk() {}
//只能抛Strike,Foul类或其子类
public void atBat() throws PopFoul {}
}
最后,需要补充一点:异常说明不属于方法类型的一部分。