跳至主要內容

异常处理

chanchaw大约 9 分钟languagejava

Throwable和Exception的区别

1、Throwable是父类,我们看看API对Throwable类的声明:

public class Throwable 
extends Object
implements Serializable

exception是子类,API中对它的声明如下:

public class Exception
extends Throwable

2、Throwable是根基,Exception是从Throwable派生出来的。 3、Throwable中包括Exception(异常)和Error(错误)。

4、Throwable用来定义所有可以作为异常被抛出来的类,Exception专指程序本身可以处理的异常,一般性的异常。

在Java程序中,所有异常对象的根基类是Throwable,Throwable从Object直接继承而来(这是Java系统所强制要求的)。Throwable有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Exception(异常)是程序本身可以处理的异常。 Error(错误)是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。 Error是一种严重的问题,应用程序不应该捕捉它。 Exception一般可能是程序和业务上的错误,是可以恢复的。

try...return 多案例

对于try-catch-finally语句中return的执行顺序,我们都有知道,finally块中的内容会先于try中的return语句执行,如果finall语句块中也有return语句的话,那么直接从finally中返回了,这也是不建议在finally中return的原因。下面通过实验来看这几种情况的执行顺序到底是什么。

try中有return,finally中没有

public class TryCatchTest {

	public static void main(String[] args) {
		System.out.println("test()函数返回:" + test());
	}

	private static int test(){
		int i = 0;
		try {
			System.out.println("Try block executing: " + ++i);
			return i;
		}catch (Exception e){
			System.out.println("Catch Error executing: " + ++i);
			return -1;
		}finally {
			System.out.println("finally executing: " + ++i);
		}
	}
}

结果如下:

Try block executing: 1 finally executing: 2 test()函数返回:1

return的是对象时,看看在finally中改变对象属性,会不会影响try中的return结果。

public class TryCatchTest {
	public int vaule = 0;

	public static void main(String[] args) {
		System.out.println("test()函数返回:" + test().vaule);
	}

	private static TryCatchTest test(){
		TryCatchTest t = new TryCatchTest();
		try {
			t.vaule = 1;
			System.out.println("Try block executing: " + t.vaule);
			return t;
		}catch (Exception e){
			t.vaule = -1;
			System.out.println("Catch Error executing: " + t.vaule);
			return t;
		}finally {
			t.vaule = 3;
			System.out.println("finally executing: " + t.vaule);
		}
	}
}

Try block executing: 1 finally executing: 3 test()函数返回:3

try和finally中均有return

private static int test(){
	int i = 0;
	try {
		System.out.println("Try block executing: " + ++i);
		return i;
	}catch (Exception e){
		System.out.println("Catch Error executing: " + ++i);
		return -1;
	}finally {
		System.out.println("finally executing: " + ++i);
		return i;
	}
}

结果如下:

Try block executing: 1 finally executing: 2 test()函数返回:2

catch和finally中均有return

private static int test(){
	int i = 0;
	try {
		System.out.println("Try block executing: " + ++i);
		throw new Exception();
	}catch (Exception e){
		System.out.println("Catch Error executing: " + ++i);
		return -1;
	}finally {
		System.out.println("finally executing: " + ++i);
		return i;
	}
}

输出结果:

Try block executing: 1 Catch Error executing: 2 finally executing: 3 test()函数返回:3

总结

1、不管有没有出现异常,finally块中代码都会执行; 2、当try和catch中有return时,finally仍然会执行; 3、finally是在return后面的表达式运算之后执行的;

对于含有return语句的情况,这里我们可以简单地总结如下:

try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况

情况一:如果finally中有return语句,则会将try中的return语句“覆盖”掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。 情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。 情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况: 1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。 2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。

try...return 分段执行的研究

当然还有很多人探讨Finally语句的执行与return的关系,颇为让人迷惑,不知道finally语句是在try的return之前执行还是之后执行?我也是一头雾水,我觉得他们的说法都不正确,我觉得应该是:finally语句是在try的return语句执行之后,return返回之前执行。这样的说法有点矛盾,也许是我表述不太清楚,下面我给出自己试验的一些结果和示例进行佐证,有什么问题欢迎大家提出来。

finally语句在return语句执行之后return返回之前执行的

public class FinallyTest1 {

    public static void main(String[] args) {
        
        System.out.println(test1());
    }

    public static int test1() {
        int b = 20;

        try {
            System.out.println("try block");

            return b += 80; 
        }
        catch (Exception e) {

            System.out.println("catch block");
        }
        finally {
            
            System.out.println("finally block");
            
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
        }
        
        return b;
    }
    
}

运行结果是:

try block
finally block
b>25, b = 100
100

说明return语句已经执行了再去执行finally语句,不过并没有直接返回,而是等finally语句执行完了再返回结果。如果觉得这个例子还不足以说明这个情况的话,下面再加个例子加强证明结论:

public class FinallyTest1 {

    public static void main(String[] args) {
        
        System.out.println(test11());
    }
    
    public static String test11() {
        try {
            System.out.println("try block");

           return test12();
      } finally {
           System.out.println("finally block");
       }
  }

  public static String test12() {
       System.out.println("return statement");

       return "after return";
   }
    
}

运行结果为:

try block
return statement
finally block
after return

说明try中的return语句先执行了但并没有立即返回,等到finally执行结束后再这里大家可能会想:如果finally里也有return语句,那么是不是就直接返回了,try中的return就不能返回了?看下面。

finally块中的return语句会覆盖try块中的return返回

public class FinallyTest2 {

    public static void main(String[] args) {

        System.out.println(test2());
    }

    public static int test2() {
        int b = 20;

        try {
            System.out.println("try block");

            return b += 80;
        } catch (Exception e) {

            System.out.println("catch block");
        } finally {

            System.out.println("finally block");

            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }

            return 200;
        }

        // return b;
    }

}

运行结果是:

try block
finally block
b>25, b = 100
200

这说明finally里的return直接返回了,就不管try中是否还有返回语句,这里还有个小细节需要注意,finally里加上return过后,finally外面的return b就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。这里大家可能又想:如果finally里没有return语句,但修改了b的值,那么try中return返回的是修改后的值还是原值?看下面。

如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。

案例1

public class FinallyTest3 {

   public static void main(String[] args) {

       System.out.println(test3());
   }

   public static int test3() {
       int b = 20;

       try {
           System.out.println("try block");

           return b += 80;
       } catch (Exception e) {

           System.out.println("catch block");
       } finally {

           System.out.println("finally block");

           if (b > 25) {
               System.out.println("b>25, b = " + b);
           }

           b = 150;
       }

       return 2000;
   }

}

运行结果是:

try block
finally block
b>25, b = 100
100

案例2

import java.util.*;

public class FinallyTest6
{
    public static void main(String[] args) {
        System.out.println(getMap().get("KEY").toString());
    }
     
    public static Map<String, String> getMap() {
        Map<String, String> map = new HashMap<String, String>();
        map.put("KEY", "INIT");
         
        try {
            map.put("KEY", "TRY");
            return map;
        }
        catch (Exception e) {
            map.put("KEY", "CATCH");
        }
        finally {
            map.put("KEY", "FINALLY");
            map = null;
        }
         
        return map;
    }
}

运行结果是:

FINALLY 为什么测试用例1中finally里的b = 150;并没有起到作用而测试用例2中finally的map.put("KEY", "FINALLY");起了作用而map = null;却没起作用呢?这就是Java到底是传值还是传址的问题了,具体请看精选30道Java笔试题解答,里面有详细的解答,简单来说就是:Java中只有传值没有传址,这也是为什么map = null这句不起作用。这同时也说明了返回语句是try中的return语句而不是 finally外面的return b;这句,不相信的话可以试下,将return b;改为return 294,对原来的结果没有一点影响。这里大家可能又要想:是不是每次返回的一定是try中的return语句呢?那么finally外的return b不是一点作用没吗?请看下面。

try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况。

public class FinallyTest4 {

    public static void main(String[] args) {

        System.out.println(test4());
    }

    public static int test4() {
        int b = 20;

        try {
            System.out.println("try block");

            b = b / 0;

            return b += 80;
        } catch (Exception e) {

            b += 15;
            System.out.println("catch block");
        } finally {

            System.out.println("finally block");

            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }

            b += 50;
        }

        return 204;
    }

}

运行结果是:

try block
catch block
finally block
b>25, b = 35
85

这里因 为在return之前发生了除0异常,所以try中的return不会被执行到,而是接着执行捕获异常的catch 语句和最终的finally语句,此时两者对b的修改都影响了最终的返回值,这时return b;就起到作用了。当然如果你这里将return b改为return 300什么的,最后返回的就是300,这毋庸置疑。 这里大家可能又有疑问:如果catch中有return语句呢?当然只有在异常的情况下才有可能会执行,那么是在finally之前就返回吗?看下面。

当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样

public class FinallyTest5 {

    public static void main(String[] args) {

        System.out.println(test5());
    }

    public static int test5() {
        int b = 20;

        try {
            System.out.println("try block");
            
            b = b /0;

            return b += 80;
        } catch (Exception e) {

            System.out.println("catch block");
            return b += 15;
        } finally {

            System.out.println("finally block");

            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }

            b += 50;
        }

        //return b;
    }

}

运行结果如下:

try block
catch block
finally block
b>25, b = 35
35

说明了发生异常后,catch中的return语句先执行,确定了返回值后再去执行finally块,执行完了catch再返回,finally里对b的改变对返回值无影响,原因同前面一样,也就是说情况与try中的return语句执行完全一样。

总结

finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。

try/catch/finally/return 总结

  • finally 中有 return
    会覆盖 try / catch 中的 return,即先执行 try 和 catch 中的逻辑(如果出现异常才会执行 catch 中的逻辑)然后执行 finally 中的逻辑并返回

  • finally 中没有 return

    • finally 中修改了 try 或者 catch 中即将返回的数据
      • 返回的数据是非包装类,即基本类型,那么返回的数据定格为 try 或者 catch 中返回的值,即不会被 finally 中的逻辑修改
      • 返回的是包装类型,则 finally 中的修改逻辑会生效