04-异常处理

异常处理

问:Java中的两种异常类型是什么?他们有什么区别?

Throwable包含了错误(Error)和异常(Excetion)两类。


图 1

(1) Error:由Java虚拟机生成并抛出,属于JVM底层或者运行时环境的错误,程序无法预测和捕获处理,如OutOfMemoryError,如果出现Error,Java虚拟机会终止执行。

例1:选择题

If a process reports a stack overflow run-time error, what’s the most possible cause?

a. lack of memory;
b. write on an invalid memory space;
c. recursive function calling;
d. array index out of boundary

答案:c

如果写了不能迅速收敛的递归,则很有可能引发栈溢出的错误,如下所示

public class Main {
    public static void main(String[] args) {
        main(null);
    }
}

(2) Exception: 用户程序可以捕获的异常情况,Java中有两种异常,分别为运行时异常(RuntimeException, 又叫非检查异常)和非运行时异常(又叫检查异常)。

  • 运行时异常 (不处理,编译可以通过,如果有异常则直接抛到控制台):RuntimeException类及其子类,如ArithmeticException、NullPointerException、IndexOutOfBoundsException、ClassCastException、BufferOverflowException、ConcurrentModificationException、IllegalArgumentException、NoSuchElementException、UnsupportedOperationException等,这类异常是在程序运行的时候可能会发生的,Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try…catch语句捕获它,也没有用throws子句声明抛出它,还是会编译通过。所以程序可以捕捉,也可以不捕捉。这些异常一般是由程序的逻辑错误引起的,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大,建议不要用try…catch…捕获处理,程序应该在开发调试的过程中从逻辑角度去尽量避免,例如:空值处理。

  • 非运行时异常 (不处理,编译不能通过):运行时异常以外的异常,也是Exception及其子类,这些异常从程序的角度来说是必须经过捕捉检查处理的(要么用 try…catch捕获处理,要么用throws语句声明抛出),否则不能通过编译。如IOException、SQLException、NoSuchMetodException、ClassNotFoundException、FileNotFoundException等。

问:异常处理语句的语法规则

  • try是用于检测被包住的语句块是否出现异常,如果有异常,则抛出异常,并执行catch语句
  • cacth用于捕获从try中抛出的异常并作出处理
  • finally语句块是不管有没有出现异常都要执行的内容
  • throw用于抛出异常
  • throws关键字可以在方法上声明该方法要抛出的异常

(1) try代码块中包含可能产生异常的代码,其后跟一个或多个catch代码块,每个catch代码块用于捕获并处理一种特定类型的异常。

(2) 当try代码块中产生异常时,程序会终止当前的执行流程,Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,然后其他的catch代码块将不会再执行,即一个异常只能被一个catch代码块捕获执行。如果try代码块没有异常产生,所有的catch代码块将跳过不执行。

(3) catch代码块后可以跟finally代码块,无论try代码块中是否抛出异常,finally代码块都会被执行,因此finally代码块为异常处理提供一个统一的出口,使得在控制流程跳转到程序的其他部分之前,能够对程序的状态作统一的管理。通常在finally代码块中可以进行资源的清除工作。

(4) try代码块后基类异常的捕获语句不可以写在子类异常捕获语句的上面。

例1:判断程序输出

public class Main {
    public static void main(String[] args) throws Exception {
        try {
            try {
                throw new Sneeze();
            } catch (Annoyance a) {
                System.out.println("Caught Annoyance");
                throw a;
            }
        } catch (Sneeze s) {
            System.out.println("Caught Sneeze");
            return;
        } finally {
            System.out.println("Hello World!");
        }
    }
}

class Annoyance extends Exception {
}

class Sneeze extends Annoyance {
}

输出:
Caught Annoyance
Caught Sneeze
Hello World!

例2:类ExampleA继承Exception,类ExampleB继承ExampleA,执行下面这段代码的输出是什么?

try {
    throw new ExampleB("b")
} catch(ExampleA e){
    System.out.println("ExampleA");
} catch(Exception e){
    System.out.println("Exception");
}

//输出:ExampleA

(5) 在try-catch-finally结构中,可重新抛出异常。

(6) try-catch-finally结构可嵌套。

(7) try不必一定有catch,只要catch和finally有一个存在就行,但是catch和finally不可以同时省略

(8) 自定义异常要继承Exception或Exception的子类。

问:异常处理完成以后,Exception对象会发生什么变化?

Exception对象会在下一个垃圾回收过程中被回收掉。

问:throw和throws有什么区别?

  • throw关键字用来在方法内部明确地抛出异常,后面跟的是要抛出的异常对象且只能跟一个异常对象;

  • throws关键字用在声明方法上,表示该方法可能要抛出的、该方法不能处理的异常,后面跟的是异常类名,可以有多个,用逗号隔开,这样方法的调用者才能够确保处理可能发生的异常。

问:finally

finally作为异常处理的一部分,只能用在try/catch语句块后,无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。

特例:如果try或catch代码块中有exit(0就不会再执行finally代码块了。

例1:当程序执行到try{}语句中的return方法时,它将要返回的结果存储到一个临时栈中,然后程序不会立即返回,而是去执行finally{}中的代码,在执行a = 2时,程序仅仅是覆盖了a的值,但不会去更新临时栈中的那个要返回的值。finally{}执行完之后,程序就会将临时栈中的值取出来返回。

public abstract class Test {
    public static void main(String[] args) {
        System.out.println(beforeFinally());  // output: 1
    }

    public static int beforeFinally(){
        int a = 0;
        try{
            a = 1;
            return a;
        }finally{
            a = 2;
        }
    }
}

例2:这里finally{}里也有一个return,那么在执行这个return时,就会更新临时栈中的值。同样,在执行完finally之后,程序将临时栈中的值取出来返回,即返回值是2.

public abstract class Test {
    public static void main(String[] args) {
        System.out.println(beforeFinally());  //output: 2
    }

    public static int beforeFinally(){
        int a = 0;
        try{
            a = 1;
            return a;
        }finally{
            a = 2;
            return a;
        }
    }
}

例3:try/catch可以捕获并处理异常,异常被捕获后程序可以继续向后执行

public class Test {
    public static void main(String[] args) {
        try {
            int i = 100 / 0;
            System.out.print(i);
        } catch (Exception e) {
            System.out.print(1);
        } finally {
            System.out.print(2);
        }
        System.out.print(3);
    }
}
// output: 123

例4:catch语句块里面,打印完1之后,又抛出了一个RuntimeException,程序并没有处理它,而是直接抛出,因此执行完finally语句块之后,程序便不往下执行直接终止了

public class Test {
    public static void main(String[] args) {
        try {
            int i = 100 / 0;
            System.out.print(i);
        } catch (Exception e) {
            System.out.print(1);
            throw new RuntimeException();
        } finally {
            System.out.print(2);
        }
        System.out.print(3);
    }
}
// output: 12

例5:catch语句块里面,打印完1之后,程序先执行完finally语句块,然后return。

public class Test {
    public static void main(String[] args) {
        try {
            int i = 100 / 0;
            System.out.print(i);
        } catch (Exception e) {
            System.out.print(1);
            return;
        } finally {
            System.out.print(2);
        }
        System.out.print(3);
    }
}
// output: 12

注意:平常我们在使用一些资源时,一般会在finally中进行资源的释放,如下形式:

BufferedReader br = new BufferedReader(new FileReader(path));    
try {    
    return br.readLine();    
} finally {    
       br.close();    
}

JDK1.7之后我们可以使用如下形式:

try (BufferedReader br = new BufferedReader(new FileReader(path)) {    
       return br.readLine();    
} 
  • 直接在try中进行声明,跟在finally里面的关闭资源类似;
  • 按照声明逆序关闭资源;
  • 这些资源都必须实现java.lang.AutoCloseable接口。

问:final关键字、finally代码块和finalize()方法有什么区别?

  • final关键字用于声明变量、方法和类,分别表示变量的值不可被修改、方法不可被重写、类不可被继承。

  • finally作为异常处理的一部分,只能用在try/catch语句块后,无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。

  • finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由垃圾收集器来调用的。可以覆盖此方法在垃圾收集时的进行其他资源回收,例如关闭文件等。但是JVM不保证此方法总会被调用。

分享到 评论