Java异常分类
Java中的异常都继承自Throwable类。
Throwable分为两大类:Error与Exception。Error一般表示系统错误,如栈溢出、堆溢出等等,一般不用我们关心。Exception是程序本身可以捕获并且可以处理的异常,是我们需要重点关注的。
上图列出了Java中常见的异常,仍有许多异常没有列出。比如IOException下就有几十种异常。
Exception类分为运行时异常和编译异常。
- 运行时异常(RuntimeException):也称非检查性异常(unchecked exception),表示程序在运行期间可能出现的错误。最典型的就是空指针异常(NullPointerException)、数组下标越界。在程序中可以选择捕获处理,也可以不处理。运行时异常可以通过预先检查进行规避,而不应该通过 catch 来处理(阿里巴巴Java开发手册)。
- 编译异常:也称检查性异常(checked exception)。Exception中除RuntimeException及其子类之外的异常都属于编译异常。在程序中必须对该类型异常进行处理,否则编译不通过。处理的方式包括向上抛出异常或try-catch捕获异常。
自定义异常
如果因业务需要或已有的异常无法表示,这时可以自定义异常。自定义异常的方式非常简单。
class MyException extends Exception {
private static final long serialVersionUID = 1L;
public MyException(String msg) {
super(msg);
}
}
public class ExceptionTest {
public void f() throws MyException {
throw new MyException("MyException in f()");
}
public static void main(String[] args) {
ExceptionTest test = new ExceptionTest();
try {
test.f();
} catch (MyException e) {
e.printStackTrace();
}
}
}
输出结果:
test.MyException: MyException in f()
at test.ExceptionTest.f(ExceptionTest.java:15)
at test.ExceptionTest.main(ExceptionTest.java:21)
e.printStackTrace()将此异常及其追踪输出至标准错误流。
异常处理
从Java 7 开始,catch语句可以一次捕获多个异常,称为multi-catch,如下所示:
public class ExceptionTest {
public void f() throws MyException, SecurityException, IOException {
throw new MyException("MyException in f()");
}
public static void main(String[] args) {
ExceptionTest test = new ExceptionTest();
try {
test.f();
} catch (SecurityException | IOException | MyException e) {
e.printStackTrace();
}
}
}
try-with-resources
从 java 1.7 开始,使用 try-with-resources 可以自动关闭实现了 AutoCloseable 或者 Closeable 接口的资源,无论它是正常执行还是抛出异常。
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
try-with-resources 语句可以同时有 catch 块和 finally 块。这种情况下,catch 块和 finally 块会在 resources 都关闭后再运行。
在使用finally块中的场景中,如果 try 和 finally 块中的语句都抛出异常,最终抛出的是 finally 中的异常,try 中的异常会被抑制。
在使用 try-with-resources 的场景中,如果 try 和 try-with-resources 中的语句都抛出异常,最终抛出的是 try 中的异常,try-with-resources 中的异常会被抑制。
可以通过Throwable.getSuppressed()方法获得被抑制的异常。
public class SuppressedExceptions {
static class CloseExceptions implements Closeable {
@Override
public void close() throws IOException {
throw new IOException("Exception from CloseExceptions.close");
}
}
public static void main(String[] args) {
try (CloseExceptions ce = new CloseExceptions()) {
throw new IOException("Exception from try block!");
} catch (Exception e) {
System.out.println(Arrays.toString(e.getSuppressed()));
}
}
}
输出结果:
[java.io.IOException: Exception from CloseExceptions.close]
虽然我们可以用异常的父类代替多个子类,如下所示:
try {
test.f();
} catch (Throwable e) {
e.printStackTrace();
}
但不推荐这样做,不同的异常代表着不同的错误,分别处理有助于故障排查与处理。
将异常通过日志输出
class MyException extends Exception {
private static final long serialVersionUID = 1L;
Logger log = Logger.getLogger(this.getClass().getName());
public MyException(String msg) throws SecurityException, IOException {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
Handler handler = new FileHandler("e:/info.txt");
log.addHandler(handler);
log.warning(trace.toString());
}
}
public class ExceptionTest {
public void f() throws MyException, SecurityException, IOException {
throw new MyException("MyException in f()");
}
public static void main(String[] args) throws SecurityException, IOException {
ExceptionTest test = new ExceptionTest();
try {
test.f();
} catch (MyException e) {
System.out.println("caught it!");
}
}
}
运行后异常将显示在控制台,并输出到info.txt文件中。
此处用的日志系统是Java自带的logging工具。事实上现在有很多优秀的日志框架实现,如Log4j、Logback等,可以用SLF4J日志门面方便的切换具体的日志实现。