一、Java异常机制
java中的异常都继承自Throwable类,其子类分成Error和Exception。其中不需要我们捕获去处理的只有Error和RuntimeException,原因如下:
- Error是发生了严重错误,程序一般对此无能为力,比如OutOfMemoryError\NoClassDefFoundError\StackOverFlowError,即便我们捕获了也无能为力;
- RunTimeException类及其子类产生的原因往往是因为糟糕的编程引起的,比如NullPointerException,我们应该避免这类编程错误,而不是靠trycatch来处理;
- 上面两类Error和RuntimeException都是运行时错误,编译器在编译阶段是不会检查的异常,我们称之为非受查异常。除了Error和RuntimeException及其子类其它的称为受查异常,在编译时强制检查的异常,Exception类的IOException类及其子类就是受查异常,这类异常如果没有使用try-catch去捕获异常并处理或者throws/throw向上抛出异常,编译不会通过,这类错误往往不是因为程序引起的错误,如读取的文件不存在,在编程阶段往往是无法避免的,因此需要处理这些异常。
编程中应该注意的点:
二、Try catch finally的执行机制
最近经常会被Try catch finally中各种return的执行顺序所困扰,在这里记录一下。
比较好的博客:https://www.cnblogs.com/lanxuezaipiao/p/3440471.html
直接看程序会更明了:
问题:请给出下面例子的输出结果
例1:
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
说明try中的return语句执行之后,返回之前会执行finally中的代码。
例2:如果finally里也有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语句覆盖返回值,那么原来的返回值可能因为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,而不是TRY,也不是null。
说明:测试用例1中对变量b重新赋值,不会影响到try中return结果,测试用例2中对map重新赋值为null,也不会影响到try中return的map,但是对map的key进行重新赋值会最终影响到try中return的map,原因是java中是都是值传递而不是引用传递。
例四:是不是每次返回的一定是try中的return语句呢?那么finally外的return b不是一点作用没吗?
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 b;
}
}
输出:
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,这毋庸置疑。
例5:如果catch中有return语句呢?当然只有在异常的情况下才有可能会执行,那么是在finally之前就返回吗?
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执行情况与未发生异常时try中return的执行情况完全一样。发生异常后,catch中的return语句先执行,确定了返回值后再去执行finally块,执行完了catch再返回,finally里对b的改变对返回值无影响,原因同前面一样。
总结为一句话:
finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。
三、值传递与引用传递
比较好的博文:博客、博客2
推荐大家多看下博客2。
先总结一句:java中都是值传递。
不用特别纠结于什么是值传递什么是引用传递,大多数人一般认为java内的基础类型数据传递都是值传递. java中实例对象的传递是引用传递,其实这是错误的,java中只有值传递,因为都是传递的副本,基本类型是传递value的副本,引用类型是传递地址的副本。本质上都是值传递。
博客2中举了一个很好的例子:
你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙,而是钥匙打开的房子。
记住三种情形,就可以了:
- 情形一:给函数传的参数是基本类型,那么改变参数值不会影响到外面的参数,因为传递的是副本;
- 情形二:给函数传的参数是引用类型,如一个map,此时如果改变map中的key,会影响到原来的map,因为相当于“砸了房子里的电视”;
- 情形三:情形二中如果对map重新赋值,比如 map = new Map<>();此时不会影响到原来的map,因为这个“钥匙”是个副本,只是将它的引用指向了一个新的地址,但没有改变原来钥匙的指向。
错误的理解:
- 错误理解1:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
- 错误理解2:Java是引用传递。
- 错误理解3:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。