异常处理过程
1. 使用 new 在堆上创建异常对象
2. 当前执行路径被终止,并从当前环境中弹出对异常对象的引用
3. 异常处理机制接管程序,并开始寻找异常处理程序继续执行 —— 将程序从错误状态中恢复
异常处理的两种基本模型
1. 终止模型:假设错误非常关键,出错以后不能恢复,不能继续执行
2. 恢复模型:异常处理程序的工作是修正错误,然后重新调用出问题的方法
捕获异常
Java 强制要求当调用的代码会抛出异常时,要么捕获抛出的异常,要么交给上层处理即在方法声明出添加异常说明。但是对于 RuntimeException(即不受检查的异常) ,编译器并没有强制要求上述规定。如果不捕获这种类型异常,那么他会穿越所有的执行路径直达 main 方法,并在程序退出前调用异常的 printStackTrace 方法。
public class Outer {
public void get() throws NullPointerException {
throw new NullPointerException("myException"){
@Override
public String getMessage() {
return "myNullPointerException:" + super.getMessage();
}
};
}
public static void main(String[] args){
Outer outer = new Outer();
outer.get();
}
}
/*
Exception in thread "main" lirui.Outer$1: myNullPointerException:myException
at lirui.Outer.get(Outer.java:11)
at lirui.Outer.main(Outer.java:20)
*/
这是由于 RuntimeException 代表的是变成错误:
1. 无法预料的错误:比如从控制范围之外传递进来的 null 引用。
2. 作为程序员,需要在代码中检查的错误。如 ArrayIndexOutOfBoundsException 。程序员在代码中需要检查一下范围。
实际上声明抛出不受检查的异常意义不是特别大,首先是因为不是检查的异常的定义决定你无法完全的预测会抛出那些异常,其次就算申明了这些异常,客户端程序员也会忽略掉。
使用 finally 进行清理
当需要把除内存之外的资源恢复到他们的初始状态时,需要用到 finally 子句。例如:已经打开的文或网络链接,在屏幕上画的图形等等。
- 即使没有捕获异常,也可以使用 try/finally 语句来强制执行 finally 中的程序段。
- 在 finaly 中使用 return :
public class Outer {
public int get() {
try{
System.out.println("before return");
return 1;
}finally { //在 try 块返回前调用 finally
return 2;
}
}
public static void main(String[] args){
Outer outer = new Outer();
System.out.println(outer.get());
}
}
/*
output:
before return
2
*/
public class Outer {
public int get() {
try{
throw new RuntimeException();
}catch (Exception e){
System.out.println("before catch return");
return 1;
}finally { //在 catch 返回前执行 finally
System.out.println("before finally return");
return 2;
}
}
public static void main(String[] args){
Outer outer = new Outer();
System.out.println(outer.get());
}
}
/*
output:
before catch return
before finally return
2
*/
异常丢失
在有些情况下会使用 finally 会导致异常丢失,使得不该被忽略的异常被忽略掉。
public class Outer {
public int get() {
try {
throw new RuntimeException("try exception");
}finally { // finally 中抛出的异常覆盖了 try 中的异常,导致异常丢失
throw new RuntimeException("finally exception");
}
}
public static void main(String[] args){
Outer outer = new Outer();
outer.get();
}
}
/*
output:
Exception in thread "main" java.lang.RuntimeException: finally exception
at lirui.Outer.get(Outer.java:14)
at lirui.Outer.main(Outer.java:19)
*/
public class Outer {
public int get() {
try{
throw new RuntimeException("try exception");
}finally { //在 finally 中使用 return ,导致异常丢失
return 2;
}
}
public static void main(String[] args){
Outer outer = new Outer();
System.out.println(outer.get());
}
}
/*
output:
2
*/
public class Outer {
public void get() {
try {
throw new RuntimeException("try exception");
}catch (Exception e){
// 处理异常过程中调用的方法产生了异常
f();
// do something else
System.out.println("异常处理完毕");
}
}
public static void main(String[] args){
Outer outer = new Outer();
outer.get();
}
private void f() {
throw new RuntimeException("f() exception");
}
}
/*
output:
Exception in thread "main" java.lang.RuntimeException: f() exception
at lirui.Outer.f(Outer.java:25)
at lirui.Outer.get(Outer.java:15)
at lirui.Outer.main(Outer.java:22)
*/
public class Outer {
public void get() {
try {
throw new RuntimeException("try exception");
}catch (Exception e){
// 处理异常过程中调用的方法产生了异常
f();
// do something else
System.out.println("异常处理完毕");
}finally {
throw new RuntimeException("finally exception");
}
}
public static void main(String[] args){
Outer outer = new Outer();
outer.get();
}
private void f() {
throw new RuntimeException("f() exception");
}
}
/*
Exception in thread "main" java.lang.RuntimeException: finally exception
at lirui.Outer.get(Outer.java:19)
at lirui.Outer.main(Outer.java:24)
*/
异常的限制
- 当覆盖方法时,只能抛出在基类方法的异常说明中列出的那些异常即抛出的异常范围只能缩小。当然,覆盖的方法可以完全不抛出异常。
- 上述限制对构造器不起作用,构造器必须包含基类构造器的异常说明(不受检查的异常除外), 派生类构造器不能捕获基类构造器抛出的异常。因为实例化子类时会需要实例化父类,父类实例化产生问题时,不能允许子类实例化。而范围可以扩大是因为子类实例化时不存在向上转型的问题。
public class Outer {
public Outer() throws InterruptedException,SQLException {}
public void get() throws InterruptedException,SQLException {}
public class Inner extends Outer {
//必须实现子类构造器并且声明父类声明的全部异常,范围可以扩大
public Inner() throws InterruptedException, SQLException,XMLParseException {}
//范围可以缩小
public void get() throws InterruptedException {}
}
}
- 即使方法定义中不抛出任何异常,也可以在方法声明中声明,这样的好处是方便后期扩展。
finally 使用的时机
我们在上文分析了 finally 适用的场景即做一些必要的清理工作,但是简单的使用 finally 可能会产生问题。下面的代码是一个错误的示范,使用 finally 来保证即使发生异常时文件也能关闭,但是当 BufferedReader 实例化产生异常时,关闭文件也就无法工作。即只有当文件被打开之后,才应该执行关闭操作。
public class InputFile {
public void readFile(String fileName){
try {
BufferedReader in = new BufferedReader(new FileReader(fileName));
//do something that might throw exception
}catch (FileNotFoundException e) {
System.out.println("could not open " + fileName);
}catch(Exception e) {
//other exception
}finally {
try {
in.close();
} catch (IOException e) {
System.out.println("in.close() failed");
}
}
}
}
正确的写法:
public class InputFile {
public void readFile(String fileName){
try {
BufferedReader in = new BufferedReader(new FileReader(fileName));
try {
//do something that might throw exception
}catch(Exception e) {
}finally {
try {
in.close();
}catch (IOException e) {
System.out.println("in.close() failed");
}
}
} catch (FileNotFoundException e) {
System.out.println("could not open " + fileName);
}
}
}
这种通用的清理惯用法的基本规则是:在创建需要清理的对象之后,立即进入一个 try/finally 语句块。
public class FinallyTest {
//单个构造器不会失败
public void testOne(){
B obj = new B();
try{
//other code
}catch (Exception e){
System.out.println("error");
}finally {
obj.dispose();
}
}
//单个构造器会失败
public void testTwo(){
try{
A obj = new A();
try{
//other code
}catch (Exception e){
System.out.println("error");
}finally {
obj.dispose();
}
}catch (SQLException e){
System.out.println("A() error");
}
}
//多个构造器不会失败
public void testThree(){
B obj1 = new B();
B obj2 = new B();
try{
//other code
}catch (Exception e){
System.out.println("error");
}finally {
obj1.dispose();
obj2.dispose();
}
}
//多个构造器会失败
public void testFive(){
try{
A obj1 = new A();
try{
try{
A obj2 = new A();
try{
//other code
}catch (Exception e){
System.out.println("error");
}finally {
obj2.dispose();
}
}catch (SQLException e){
System.out.println("obj2 A() error");
}
}finally {
obj1.dispose();
}
}catch (SQLException e){
System.out.println("obj1 A() error");
}
}
}
class A {
public A() throws SQLException {}
public void dispose(){}
}
class B {
public B(){}
public void dispose(){}
}