Java 基础 - 异常处理
# 异常分类
# Error 与 Exception
在 Java 语言中,所有的异常类都是 Throwable 类的子类,Throwable 有两个子类分支,分别是 Error 和 Exception,所以 Java 中的异常也主要分为 Error 和 Exception 两类,类图关系如下图所示。且如果 Java 内置的异常类不能满足需求,开发者还可以自定义异常类。
- Error:Error 及其子类描述了 Java 运行时系统的内部错误和资源耗尽错误,是 Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。比如:StackOverflowError 和 OOM。在程序中不应该抛出此类错误,也不能在代码中针对此类错误进行处理,只能通知用户去优化代码或外部资源以避免此类错误出现。
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。Exception 中又可以分为两个子类分支,一个是 RuntimeException,通常称为运行时异常;另一个分支是除 RuntimeException 之外的其它类,这部分异常通常被称为非运行时异常或编译时异常。
- 编译时异常:是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求 Java 程序必须捕获或声明所有编译时异常。对于这类异常,如果程序不处理,可能会带来意想不到的结果。如:IOException、文件不存在、找不到类等。
- 运行时异常:是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。RuntimeException 类及它的子类都是运行时异常。如:数组下标越界、空指针、类型强转错误。
# 检查型异常与非检查型异常
Java 中派生于 Error 类或 RuntimeException 类的所有异常被称为非检查型异常,其它所有异常都称为检查型异常。其实这个很好理解,因为除了派生于 Error 类和 RuntimeException 类的异常都是编译时异常,编译时异常会在程序编译期间就需要处理,除了编译时异常之外的异常只有在程序运行期间才会出现。
# 异常处理机制
Java 中的异常处理机制有两种:声明异常和捕获异常。无论通过何种方式处理,前提是有异常抛出,且能够使用异常处理机制处理的异常是 Exception 及其子类。
异常对象的生成:
- 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出。
- 由开发人员手动创建:
Exception exception = new ClassCastException();
。创建好的异常对象如果不抛出则对程序没有任何影响,和创建一个普通对象一样。
# 抛出异常
当程序中可能出现某种错误时,可以使用throw
关键字抛出一个异常类的实例,如:
public static void main(String[] args) {
Integer value = Integer.parseInt(args[0]);
if (value < 0){
throw new IllegalArgumentException();
}
}
2
3
4
5
6
7
大部分情况下是不需要自己手动抛出异常的,因为 Java 内部已经处理了,手动抛出异常更多地是针对自定义异常。
异常的抛出机制:
- 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用 方法的上层方法。这个过程将一直继续下去,直到异常被处理。
- 如果一个异常回到 main()方法,并且 main()也不处理,则程序运行终止。
- 开发者通常只能处理 Exception 及其子类,而对 Error 无能为力。
# 声明异常
Java 中如果程序执行到某个方法中,而该方法中存在某个异常,要么对该异常捕获并处理,要么将该异常继续向上抛给当前方法的调用者,让上层方法对齐进行捕获处理或继续往上一层抛出。将当前方法中存在的异常抛向其调用者的方式是在方法的签名上增加throws
关键字。
public void test() throws IOException {
// pass
}
2
3
如果要同时向上层调用方法抛出多个异常,则使用英文逗号隔开:
public void test() throws IOException, FileNotFoundException, ClassNotFoundException {
}
2
3
# 捕获异常
捕获异常是通过 try-catch-finally 语法完成的:
try {
//可能产生异常的代码
} catch (ExceptionName1 e) {
//当产生 ExceptionName1 型异常时的处置措施
} catch (ExceptionName2 e) {
//当产生 ExceptionName2 型异常时的处置措施
} finally {
//无论是否发生异常,都无条件执行的语句
}
2
3
4
5
6
7
8
9
也可以在一个 catch 子句中捕获多个异常:
try {
//可能产生异常的代码
} catch (ExceptionName1 | ExceptionName2 e) {
//当产生 ExceptionName1 型异常时的处置措施
} finally {
//无论是否发生异常,都无条件执行的语句
}
2
3
4
5
6
7
- try:捕获异常的第一步是用 try 语句块选定捕获异常的范围,将可能出现异常的代码放在 try 语句块中。
- catch(ExceptionType e):在 catch 语句块中是对异常对象进行处理的代码。每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象。
- finally:捕获异常的最后一步是通过 finally 语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
注意
- 在 catch 语句中,如果明确知道产生的是何种异常,可以用该异常类作为 catch 的参数;也可以用其父类作为 catch 的参数。比如:可以用 ArithmeticException 类作为参数的地方,就可以用 RuntimeException 类作为参数,或者用所有异常的父类 Exception 类作为参数。但不能是与 ArithmeticException 类无关的异常,如 NullPointerException(catch 中的语句将不会执行)。
- 捕获异常的有关信息:
- 与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
- getMessage() 获取异常信息,返回字符串。
- printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值 void。
- 不论在 try 代码块中是否发生了异常事件,catch 语句是否执行,catch 语句是否有异常,catch 语句中是否有 return,finally 块中的语句都会被执行。
- catch 语句和 finally 语句是可选的,可以出现,也可以不出现。
由于“catch 语句和 finally 语句是可选的,可以出现,也可以不出现”,所以 try-catch-finally 有这几种组合:
- try-catch-finally
- try-catch
- try-finally
- try(这种方式也被称为 try-with-resource)
前面三种很常见,也很好理解,可以根据业务逻辑自由选择使用哪种方式,下面针对 try-with-resource 这种方式进行说明。
try-with-resource 是 Java 7 中引入的,主要针对实现了 AutoCloseable 接口的类实现了一种更优雅的关闭方式。如:
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("/home/www/music"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
2
3
4
5
6
7
由于 Scanner 实现了 Closeable 接口,而 Closeable 接口又继承自 AutoCloseable 接口,AutoCloseable 接口中提供了 close()方法,close()方法会在 try-with-resources 语句中自动关闭资源。
public interface AutoCloseable {
/**
* Closes this resource, relinquishing any underlying resources.
* This method is invoked automatically on objects managed by the
* {@code try}-with-resources statement.
*
* <p>While this interface method is declared to throw {@code
* Exception}, implementers are <em>strongly</em> encouraged to
* declare concrete implementations of the {@code close} method to
* throw more specific exceptions, or to throw no exception at all
* if the close operation cannot fail.
*
* <p> Cases where the close operation may fail require careful
* attention by implementers. It is strongly advised to relinquish
* the underlying resources and to internally <em>mark</em> the
* resource as closed, prior to throwing the exception. The {@code
* close} method is unlikely to be invoked more than once and so
* this ensures that the resources are released in a timely manner.
* Furthermore it reduces problems that could arise when the resource
* wraps, or is wrapped, by another resource.
*
* <p><em>Implementers of this interface are also strongly advised
* to not have the {@code close} method throw {@link
* InterruptedException}.</em>
*
* This exception interacts with a thread's interrupted status,
* and runtime misbehavior is likely to occur if an {@code
* InterruptedException} is {@linkplain Throwable#addSuppressed
* suppressed}.
*
* More generally, if it would cause problems for an
* exception to be suppressed, the {@code AutoCloseable.close}
* method should not throw it.
*
* <p>Note that unlike the {@link java.io.Closeable#close close}
* method of {@link java.io.Closeable}, this {@code close} method
* is <em>not</em> required to be idempotent. In other words,
* calling this {@code close} method more than once may have some
* visible side effect, unlike {@code Closeable.close} which is
* required to have no effect if called more than once.
*
* However, implementers of this interface are strongly encouraged
* to make their {@code close} methods idempotent.
*
* @throws Exception if this resource cannot be closed
*/
void close() throws Exception;
}
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
# 自定义异常
如果在开发中遇到 Java 内置的标准异常类无法描述的问题,则可以自定义异常。自定义异常通常需要集成 Exception 类或其子类,并提供两个构造器:一个无参构造器和一个包含错误描述错误信息参数的构造器,且提供一个 serialVersionUID
。
public class ExceptionTest {
public static void main(String[] args) throws MyException {
// pass
throw new MyException("imei号不符合业务规则");
}
}
class MyException extends Exception {
private static final long serialVersionUID = -557698798856068164L;
public MyException() {
super();
}
public MyException(String msg) {
super(msg);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20