Java基础之try-with-resource语法糖

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_40088207/article/details/111508962

背景

这是一篇东拼西凑的文章。

在java开发中,一些网络链接或者是文件资源都需要程序员去手动调用close方法关闭,比如InputStream、OutputStream和java.sql.Connection。

如果忘关了就可能造成严重的性能后果。而关闭的方法有很多种。比如finalizer、try-catch-finally、try-with-resources等等。

try-with-resources

try-with-resources是jdk1.7引入的语法糖,使得关闭资源操作无需层层嵌套在finally。

finalizer 会引发内存泄漏

垃圾回收器发现对象实现了finalize()方法并把它们添加到java.lang.ref.Finalizer.ReferenceQueue队列中。

然后Finalizer线程 会把ReferenceQueue里面的对象逐个弹出。

不过由于它的优先级比主线程较低,获取到的CPU时间较少,因此它永远也赶不上主线程创建对象的步伐。

所以会引发内存泄漏,所以finalize()并不适合用作普通的清理工作,一般只用于当java中调用非java代码时重写finalize()方法,并在里面调用本地方法的free()等函数。

或者在一下时候有某些用处:

存在一系列对象,对象中有一个状态为false,如果我们已经处理过这个对象,状态会变为true,为了避免有被遗漏而没有处理的对象,就可以使用finalize()方法:


class MyObject{
 
    boolean state = false;
 
    public void deal(){
        //...一些处理操作
        state = true;
    }
 
    @Override
    protected void finalize(){
        if(!state){
            System.out.println("ERROR:" + "对象未处理!");
        }
    }
    //...


finally的执行顺序

1、finally不是必要条件
也就是说try-catch-finally中,可以只有try-catch,也可以只有try-finally。

2、假设基于try-catch-finally:
第一:代码没有异常
执行顺序:try执行完整->catch不执行->finally执行

第二:代码有异常且catch进行捕获**
执行顺序:try执行部分->跳转catch捕获处理->finally执行

第三:代码有异常且catch不捕获:这种情况没有catch**
执行顺序:try执行部分->finally执行
从上面的执行顺序可以看出,finally语句不管在哪种情况是一定会执行的。基于这个认识,现在我们再来分析。

题外话: 当finally有return时,会直接返回。不会再去返回try或者catch中的返回值,而finally没有return时,try和catch 的return语句并不会马上执行,而是执行完finally代码块之后再返回try和catch里面的值。

try-finally的缺点

同时打开了多个资源,那么将会出现噩梦般的场景:

public class Demo {
		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中嵌套的将会越深。

主要是这样子写代码是真的丑。

用try-with-resource来改写刚才的例子:

要使用try-with-resource的资源,必须先实现AutoCloseable接口,其中包含了单个返回void的close方法,Java类库与第三方类库中的许多类和接口,现在都实现或扩展了AutoCloseable接口,因此我们现在不必实现了。
public class TryWithResource {
  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(),而且可以避免异常屏蔽
看一下反编译出来的代码:

package com.codersm.trywithresource;

	public class TryWithResource {
		public TryWithResource() {
		}

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

				try {
					conn.sendData();
				} catch (Throwable var12) {
					var2 = var12;
					throw var12;
				} finally {
					if (conn != null) {
						if (var2 != null) {
							try {
								conn.close();
							} catch (Throwable var11) {
								var2.addSuppressed(var11);
							}
						} else {
							conn.close();
						}
					}

				}
			} catch (Exception var14) {
				var14.printStackTrace();
			}

		}
	}


在第15~27行,编译器自动帮我们生成了finally块,并且在里面调用了资源的close方法,所以例子中的close方法会在运行的时候被执行。

异常屏蔽

我们将刚才的代码改回远古时代手动关闭异常的方式,并且在 sendData 和 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");
		}
	}

修改main方法:

public class TryWithResource {
  public static void main(String[] args) {
    try {
      test();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  private static void test() throws Exception {
    Connection conn = null;
    try {
      conn = new Connection();
      conn.sendData();
    }
    finally {
      if (conn != null) {
        conn.close();
      }
    }
  }
}

运行之后我们发现:

basic.exception.MyException: close
 at basic.exception.Connection.close(Connection.java:10)
 at basic.exception.TryWithResource.test(TryWithResource.java:82)
 at basic.exception.TryWithResource.main(TryWithResource.java:7)
 ......

可以看到,异常信息中多了一个 Suppressed 的提示,告诉我们这个异常其实由两个异常组成, MyException 是被Suppressed的异常。可喜可贺!

仔细对比发现,是因为反编译的代码(第21行)多了一个 addSuppressed :var2.addSuppressed(var11);
从而避免了异常屏蔽的问题。

结论

处理必须关闭的资源时,始终要优先考虑使用try-with-resources,而不是try-finally。这样得到的代码将更简洁,清晰,产生的异常也更有价值,这些也是try-finally无法做到的。

参考文章:
JAVA 内存泄露详解(原因、例子及解决)
为什么推荐使用try-with-resources代替try-finally
深入理解Java基础之try-with-resource语法糖

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值