Java中的异常

Java中的异常类都继承自Throwable类,Throwable有两个子类:Exception(异常)Error(错误), 二者都各自包含大量子类。



异常的结构关系图

异常的继承结构关系图


异常的分类

Java的异常可分为:错误、不受检查异常、受检查异常。

  • Error(错误): 是程序无法处理的错误,一旦出现会导致严重后果,一般会停止运行。
  • Exception(异常): 是程序本身可以处理的异常,又分为不受检查异常和受检查异常。
    • 不受检查异常: RuntimeException及其子类都是不检查异常,该类异常可以捕捉处理,也可以不捕捉处理。
    • 受检查异常: 除了ErrorRuntimeException之外的所有异常都是受检查的,要么使用try...catch捕捉,要么在方法上声明throws

finally中的return

一般情况下finally总会被执行,即使前面执行到了return语句,在执行完之前也会执行finally,在finally中使用return语句是非常不好的习惯。

  • finallyreturn会覆盖其他return的值,如下标记B就吞掉了标记A的返回值;
  • finallyreturn会吞掉catch块中抛出的异常,如下标记D就吞掉了标记C抛出的异常。
public class TestException {
    boolean testEx() {
        boolean ret = true;
        try {
            testEx1();          /* 将不会抛出异常,因为testEx1中catch块抛出的异常被finally里的return吞掉了;
                                   若testEx1中的finally里无return,则会抛出异常
                                */
            return ret;        /* 该语句正常执行,该语句标记为A */
        } catch (Exception e) { /* 捕获不到异常 */
            System.out.println("testEx, catch exception"); //不打印
        } finally {
            System.out.println("testEx, finally");         //打印
            ret = false;
            return ret;       /* 该语句会吞掉之前标记A的return值,该语句标记为B */
        }
    }
    boolean testEx1() throws Exception {
        try {
            int a = 9 / 0;       /* 将抛出 ArithmeticException */
            return true;         /* 执行不到 */
        } catch (Exception e) {  /* 捕获异常 */
            System.out.println("testEx1, catch exception"); // 打印
            throw e;             /* 抛出异常 该语句标记为C */
        } finally {
            System.out.println("testEx1, finally"); // 打印
            return false;        /* 返回,并且catch中跑出的异常被忽略了 该语句标记为D */
        }
    }
    public static void main(String[] args) {
        TestException testException = new TestException();
        System.out.println(testException.testEx());    // 将输出标记B的返回值: false
    }
}

在finally中修改返回值

另外,由于finally块一定会执行,所以可以在函数返回结果之前修改返回值,但一定要小心,有些情况下能够修改返回值,有些情况下是无法修改的。

// TestFinally.java
public class TestFinally {
    Man test1() {
        Man man1 = new Man();
        Man man2 = new Man();
        try {
            return man1;   // 返回man1
        } finally {
            man2.setName("world");
            man1 = man2;   // 修改man1
        }
    }
    Man test2(){
        Man man1 = new Man();
        Man man2 = new Man();
        try {
            return man1;   // 返回man1
        } finally {
            man1.setName("world"); // 修改man1
        }
    }
    public static void main(String[] args) {
        TestFinally testFinally = new TestFinally();
        System.out.println(testFinally.test1());    // 输出 name:hello
        System.out.println(testFinally.test2());    // 输出 name:world
    }
}
// Man.java
public class Man{
    private String name = "hello";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "name:" + name;
    }
}

test1man1 = man2;确实执行了,但是在执行finally块之前,return的值就确定了,所以返回的man1仍是原来的; test2man1.setName("world");也执行了,并且在此之前,return的值也确定了,但这句并未改变man1的值(引用地址),而是改变了name属性的值。