简单介绍
我想每一个学习java的同学都一定遇到过这样一道经典的面试题:谈谈java中final,finally,finalize
的区别和用法。面对这道题,我想每个人或多或少都能说出他们各自的功效。确实,这三个虽然长得很像,但是作用却是完全不相同的。不过,今天的重点是finally
,所以还不太明白的同学建议自行度娘,这里不再赘述。
题外话:
finalize
在Java9中已经被标注为过时的方法,可以使用java.lang.ref.Cleaner
和java.lang.ref.PhantomReference
进行替代。
finally
表示总是执行。放入finally块中的代码,无论是否有异常发生,都会执行,他是Java对异常处理的最佳补充。所以我们常用来做数据库连接的关闭,资源的释放等操作。例如:
public static void main(String[] args) {
String sql = "select * from user";
Connection con = null;
PreparedStatement ps = null;
try {
con = DriverManager.getConnection("mysqlUrl", "root", "123456");
ps = con.prepareStatement(sql);
ps.executeQuery();
}catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if(con != null){
con.close();
}
if(ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
String file = "D:\\test.txt";
FileInputStream is = null;
try {
is = new FileInputStream(file);
int i = 0;
while ((i = is.read()) != -1) {
System.out.print((char) i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(is != null){
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这两段代码demo就很好的诠释了finally
的作用,避免在资源使用或者发生异常之后无法关闭,造成资源无法释放。
try-with-resource语法
还是上面的两个例子,可以看出,虽然进行了手动的资源关闭,但是在代码中使用了太多的try...catch...
块,不仅代码阅读不方便,而且感觉也很麻烦。我们不仅要问,难道就不能有一种方法让资源自动关闭么。
基于此,try-with-resource
语法诞生了。实际上,这是java7中提供的语法糖,将资源对象的创建放在try
语句之后的()
中,这样当try...catch...
代码块执行完毕后,Java会自动调用资源的close方法。根据描述,我们可以对上述两个例子进行改造,改造之后的代码为:
public static void main(String[] args) {
String sql = "select * from user";
try(Connection con = DriverManager.getConnection("mysqlUrl", "root", "123456");
PreparedStatement ps = con.prepareStatement(sql)){
ps.executeQuery();
}catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String file = "D:\\test.txt";
try(FileInputStream is = new FileInputStream(file)){
int i = 0;
while ((i = is.read()) != -1) {
System.out.print((char) i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
对比一下改造前后有没有发现代码看起来非常的简洁明了。
这里必须注意一下的是要想使用try-with-resource语法,该资源对象(例如demo中FileInputStream和Connection)必须实现AutoCloseable接口或者Closeable接口。
实际上Closeable接口也继承了AutoCloseable接口。
当有多个资源对象创建时,可以用;
分割。
那么try-with-resource语法
是如何实现的自动关闭呢,我们对改造的第二段代码进行反编译:
public static void main(String[] args){
String file = "D:\\test.txt";
try{
FileInputStream is = new FileInputStream(file);
Throwable localThrowable3 = null;
try{
int i = 0;
while ((i = is.read()) != -1) {
System.out.print((char)i);
}
}catch (Throwable localThrowable1){
localThrowable3 = localThrowable1;
throw localThrowable1;
}finally{
if (is != null) {
if (localThrowable3 != null) {
try{
is.close();
}catch (Throwable localThrowable2){
localThrowable3.addSuppressed(localThrowable2);
}
} else {
is.close();
}
}
}
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
}
可以看出,其实依旧是使用了finally
块来做资源的关闭。只不过减少了程序员的工作量,而且代码更加简洁美观。
异常抑制
在反编译代码中,有这样一行代码:localThrowable3.addSuppressed(localThrowable2);
这是在java7中为Throwable类增加的方法,这里涉及到一个概念,叫做异常抑制。
当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过addSuppressed
方法把这些被抑制的方法记录下来。被抑制的异常会出现在抛出的异常的堆栈信息中,可以通过getSuppressed
方法来获取这些异常。这样做的好处是不会丢失任何异常,方便开发人员进行调试。
return与finally
上面我们说过,finally块的代码是一定会执行的,而return也是终止当前方法。那么他们的先后顺序是怎样的呢。
public class Test {
public static void main(String[] args) {
String name1 = test1("王五");
System.out.println("test1运行结果为:" + name1);
String name2 = test2("王五");
System.out.println("test2运行结果为:" + name2);
String name3 = test3("王五");
System.out.println("test3运行结果为:" + name3);
}
public static String test1(String name){
try {
name = "张三";
return name;
}finally {
name = "李四";
System.out.println("test1方法finally语句块name:"+name);
}
}
public static String test2(String name){
try {
name = "张三";
}finally {
name = "李四";
System.out.println("test2方法finally语句块name:"+name);
return name;
}
}
public static String test3(String name){
try {
name = "张三";
return name;
}finally {
name = "李四";
System.out.println("test3方法finally语句块name:"+name);
return name;
}
}
}
上述代码的运行结果为:
test1方法finally语句块name:李四
test1运行结果为:张三
test2方法finally语句块name:李四
test2运行结果为:李四
test3方法finally语句块name:李四
test3运行结果为:李四
可以看出,finally语句块中的代码确实执行了,但是虽然在finally中更改了返回值,不过最后的返回值完全取决于return最终出现的位置。
所以finally执行是在return之前执行的。
而return的执行又是分为两部分的,一部分是return指令,一部分是return后面的表达式expression。
他们三者关联在一起实际的执行顺序是:
1、执行:expression,计算该表达式,结果保存在操作数栈顶;
2、执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;
3、执行:finally语句块中的代码;
4、执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
5、执行:return指令,返回操作数栈顶的值;