【Java】finally

finally

执行流程

  1. 在try或catch中有System.exit(0);这类终止JVM的的语句,那么finally语句不会执行。
  2. 若try或catch中有return,先执行return后的表达式运算,然后把要返回的值保存下来,设try中要返回的结果为x,则在跳转finally之前会先将此结果存放到不同于x的局部变量(栈)中,再执行finally,待执行完finally后再返回之前保存的值,因此,即使finally中对变量x进行了改变也不会影响返回结果(也就是说,若在finally中直接修改的是引用地址或基本数据类型,那么返回的就是拷贝之前的,若在finally中修改要返回的某个对象中的属性,则返回的是修改过后的)。
  3. 若finally块中有return则提前退出,此时返回值不是try或catch中return处保存的值,因此finally中的return是一定会被return的,编译器把finally中的return实现为一个warning。

示例

private static int test1(int x) {
    try {
        return x++;
    } finally {
        x++;
    }
}

private static int test2(int x) {
    try {
        return x++;
    } finally {
        return x++;
    }
}

private static int test3(int x) {
    try {
        return ++x;
    } finally {
        // 虽然此处执行,但对返回结果没影响,因为return处已把返回结果保存在栈中。
        ++x;
    }
}

private static int test4(int x) {
    try {
        return x++;
    } finally {
        ++x;
    }
}

private static int test5(int x) {
    try {
        return ++x;
    } finally {
        return ++x;
    }
}

private static int test6() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

public static void main(String[] args) {
    System.out.println(test1(0)); // 0
    System.out.println(test2(0)); // 1
    System.out.println(test3(0)); // 1
    System.out.println(test4(0)); // 0
    System.out.println(test5(0)); // 2
    System.out.println(test6()); // 2
}

try-with-resource

try-finally的缺点

public static void main(String[] args) {
    BufferedInputStream bin = null;
    BufferedOutputStream bout = null;
    try {
        bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
        bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
        int b;
        while ((b = bin.read()) != -1) {
            bout.write(b);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (bin != null) {
            try {
                bin.close();
            } catch (IOException e) {
                throw e;
            } finally {
                if (bout != null) {
                    try {
                        bout.close();
                    } catch (IOException e) {
                        throw e;
                    }
                }
            }
        }
    }
}

我们不仅要关闭BufferedInputStream ,还要保证若关闭BufferedInputStream时出现了异常,BufferedOutputStream也要能被正确地关闭,所以不得不使finally中嵌套finally,因此打开的资源越多,finally中的嵌套将会越深。

使用

必须先实现AutoCloseable接口并重写close方法。

public static void main(String[] args) {
    try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
         BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
        int b;
        while ((b = bin.read()) != -1) {
            bout.write(b);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

原理

编译器会自动补全close()并避免异常屏蔽

public class Connection implements AutoCloseable {
    public void sendData() throws Exception {
        throw new Exception("send data");
    }

    @Override
    public void close() throws Exception {
        throw new MyException("close");
    }

    public static void main(String[] args) {
        try (Connection conn = new Connection()) {
            conn.sendData();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反编译class文件

public static void main(String[] args) {
    try {
        Connection conn = new Connection();

        try {
            conn.sendData();
        } catch (Throwable var5) {
            try {
                conn.close();
            } catch (Throwable var4) {
                var5.addSuppressed(var4);
            }

            throw var5;
        }

        conn.close();
    } catch (Exception var6) {
        var6.printStackTrace();
    }

}

发现反编译的代码不仅补全了close,还多了addSuppressed。

异常屏蔽

Throwable类的addSuppressed方法支持将一个异常附加到另一个异常身上,从而避免异常屏蔽。

  • 使用addSuppressed
java.lang.Exception: send data
	at xyz.jshen6.common.Connection.sendData(Connection.java:5)
	at xyz.jshen6.common.Connection.main(Connection.java:15)
	Suppressed: xyz.jshen6.common.MyException: close
		at xyz.jshen6.common.Connection.close(Connection.java:10)
		at xyz.jshen6.common.Connection.main(Connection.java:14)

异常信息中多了一个Suppressed的提示,说明这个异常其实由两个异常组成,MyException是被Suppressed的异常。

  • 未使用addSuppressed
private static void test() throws Exception {
    Connection conn = null;
    try {
        conn = new Connection();
        conn.sendData();
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
}

public static void main(String[] args) {
    try {
        test();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
xyz.jshen6.common.MyException: close
	at xyz.jshen6.common.Connection.close(Connection.java:10)
	at xyz.jshen6.common.Connection.test(Connection.java:20)
	at xyz.jshen6.common.Connection.main(Connection.java:27)

由于一次只能抛出一个异常,所以在最上层看到的是最后一个抛出的异常,也就是close方法抛出的MyException,而sendData抛出的Exception被忽略了,这就是异常屏蔽。由于异常屏蔽导致异常信息的丢失,可能会使一些bug变得极难发现。

注意事项

一定要了解资源的close方法内部的实现逻辑再使用try-with-resource,否则还是可能会导致资源泄露。
在Java BIO中采用了大量的装饰器模式,当调用装饰器的close方法时,本质上是调用了装饰器内部包裹的流的close方法,比如:

public static void main(String[] args) {
    try (FileInputStream fin = new FileInputStream(new File("input.txt"));
         GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))) {
        byte[] buffer = new byte[4096];
        int read;
        while ((read = fin.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

上述代码是从FileInputStream中读取字节并写入到GZIPOutputStream中,而out变量代表的是被装饰的FileOutputStream类(GZIPOutputStream是FileOutputStream的装饰器),根据try-with-resource的特性,实际编译后的代码会调用fin.close()out.close()
再看下ZIPOutputStream的父类DeflaterOutputStream的close源码:

public void close() throws IOException {
    if (!closed) {
        finish();
        if (usesDefaultDeflater)
            def.end();
        out.close();
        closed = true;
    }
}

在调用out变量的close方法之前,GZIPOutputStream还做了finish操作,该操作会继续往FileOutputStream中写压缩信息,若此时出现异常,out.close()方法则不执行,然而这才是最底层的关闭资源的方法,所以应该在try-with-resource中单独声明最底层的资源,确保对应的close方法一定能够被调用:

public static void main(String[] args) {
    try (FileInputStream fin = new FileInputStream(new File("input.txt"));
         FileOutputStream fout = new FileOutputStream(new File("out.txt"));
         GZIPOutputStream out = new GZIPOutputStream(fout)) {
        byte[] buffer = new byte[4096];
        int read;
        while ((read = fin.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

反编译class:

public static void main(String[] args) {
    try {
        FileInputStream fin = new FileInputStream(new File("input.txt"));

        try {
            FileOutputStream fout = new FileOutputStream(new File("out.txt"));

            try {
                GZIPOutputStream out = new GZIPOutputStream(fout);

                try {
                    byte[] buffer = new byte[4096];

                    int read;
                    while((read = fin.read(buffer)) != -1) {
                        out.write(buffer, 0, read);
                    }
                } catch (Throwable var9) {
                    try {
                        out.close();
                    } catch (Throwable var8) {
                        var9.addSuppressed(var8);
                    }

                    throw var9;
                }

                out.close();
            } catch (Throwable var10) {
                try {
                    fout.close();
                } catch (Throwable var7) {
                    var10.addSuppressed(var7);
                }

                throw var10;
            }

            fout.close();
        } catch (Throwable var11) {
            try {
                fin.close();
            } catch (Throwable var6) {
                var11.addSuppressed(var6);
            }

            throw var11;
        }

        fin.close();
    } catch (IOException var12) {
        var12.printStackTrace();
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值