Java新特性-try-with-resource
来源:互联网 发布:单片机信号线太长 编辑:程序博客网 时间:2024/04/28 23:28
在我们的项目中,所有被打开的系统资源,比如流、文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重大的生产事故。
传统的try
语法
try{ //进行可能出现异常的操作}catch(捕获的异常名称){ //进行异常处理}finally{ //关闭资源操作}
实例代码如下:
public static void try1() { 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"))); } catch (IOException e) { e.printStackTrace(); } finally { if (bin != null) { try { bin.close(); } catch (IOException e) { e.printStackTrace(); } } if (bout != null) {// 确保即使BufferedInputStream在执行close()方法时发生异常,也执行BufferedOutputStream的close()方法 try { bout.close(); } catch (IOException e) { e.printStackTrace(); } } } }
- 使用finally块来关闭物理资源,保证关闭操作总是会被执行。
- 关闭每个资源之前首先保证引用该资源的引用变量不为null。
- 为每一个物理资源使用单独的try…catch块来关闭资源,保证关闭资源时引发的异常不会影响其他资源的关闭。
- 以上方式导致finally块代码十分臃肿,关闭资源的代码比业务代码还要多,程序的可读性降低。
java7增强try
为了解决以上传统方式的问题, Java7新增了自动关闭资源的try语句。它允许在try关键字后紧跟一对圆括号,里面可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时显示关闭的资源(数据库连接、网络连接等),try语句会在该语句结束时自动关闭这些资源。
语法
try(初始化可能出现异常的操作){ //进行操作}ctach(捕获的异常名称){ //进行异常操作}
打开一个资源
try( 打开一个资源的语句;//末尾可以加";",也可以不加){ //进行操作}
打开多个资源
try( 打开一个资源的语句; 打开一个资源的语句; 打开一个资源的语句;//多个表达式末尾加";",最后一个表达式末尾可以不加";"){ //进行操作}
改写以上代码如下:
//捕获异常 public static void try2() { try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));) { // 执行完try中的语句过后,资源自动关闭 } catch (IOException e) { e.printStackTrace(); } }
或者
//抛出异常 public static void try2() throws FileNotFoundException, IOException { try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));) { // 执行完try中的语句过后,资源自动关闭 } }
自动关闭资源的try语句相当于包含了隐式的finally块(用于关闭资源),因此这个try语句可以既没有catch块,也没有finally块。
动手测试
为了能够配合try-with-resource,资源必须实现AutoCloseable接口。且该接口的实现类需要重写close方法:
package tryTest;/** * * @ClassName: Connection * @Description: 创建实例,实现AutoCloseable接口 * @author cheng * @date 2017年8月18日 上午9:37:11 */public class Connection implements AutoCloseable { public void sendData() throws Exception { System.out.println("正在发送数据........"); } /** * 重写close方法 */ @Override public void close() throws Exception { System.out.println("正在自动关闭连接........"); }}
测试
public static void try3() { try (Connection conn = new Connection();) { conn.sendData(); } catch (Exception e) { e.printStackTrace(); } }
运行结果:
正在发送数据........正在自动关闭连接........
通过结果我们可以看到,close方法被自动调用了。
异常屏蔽处理
修改Connection,模拟抛出异常
package tryTest;/** * * @ClassName: Connection * @Description: 创建实例,实现AutoCloseable接口 * @author cheng * @date 2017年8月18日 上午9:37:11 */public class Connection implements AutoCloseable { public void sendData() throws Exception { System.out.println("正在发送数据........"); throw new Exception("模拟发送时发生了异常....."); } /** * 重写close方法 */ @Override public void close() throws Exception { System.out.println("正在自动关闭连接........"); throw new Exception("模拟关闭时发生了异常....."); }}
使用传统的try,手动关闭异常
public static void try5() throws Exception { Connection conn = null; try { conn = new Connection(); conn.sendData(); } finally { if (conn != null) { conn.close(); } } }
调用:
public static void main(String[] args) { try { try5(); } catch (Exception e) { e.printStackTrace(); } }
运行结果:
正在发送数据........正在自动关闭连接........java.lang.Exception: 模拟关闭时发生了异常..... at tryTest.Connection.close(Connection.java:23) at tryTest.TryTest.try5(TryTest.java:40) at tryTest.TryTest.main(TryTest.java:26)
好的,问题来了,由于我们一次只能抛出一个异常,所以在最上层看到的是最后一个抛出的异常——也就是close方法抛出的模拟异常,而sendData抛出的Exception被忽略了。这就是所谓的异常屏蔽。
由于异常信息的丢失,异常屏蔽可能会导致某些bug变得极其难以发现,程序员们不得不加班加点地找bug.
为了解决这个问题,从Java 1.7开始,大佬们为Throwable类新增了addSuppressed方法,支持将一个异常附加到另一个异常身上,从而避免异常屏蔽。那么被屏蔽的异常信息会通过怎样的格式输出呢?我们再运行一遍刚才用try-with-resource包裹的main方法:
public static void try3() { try (Connection conn = new Connection();) { conn.sendData(); } catch (Exception e) { e.printStackTrace(); } }
运行结果:
正在发送数据........正在自动关闭连接........java.lang.Exception: 模拟发送时发生了异常..... at tryTest.Connection.sendData(Connection.java:14) at tryTest.TryTest.try3(TryTest.java:59) at tryTest.TryTest.main(TryTest.java:23) Suppressed: java.lang.Exception: 模拟关闭时发生了异常..... at tryTest.Connection.close(Connection.java:23) at tryTest.TryTest.try3(TryTest.java:60) ... 1 more
可以看到,异常信息中多了一个Suppressed的提示,告诉我们这个异常其实由两个异常组成.
打开多个资源时:单独声明每个资源
在使用try-with-resource的过程中,一定需要了解资源的close方法内部的实现逻辑。否则还是可能会导致资源泄露。
比如,在Java BIO中采用了大量的装饰器模式。当调用装饰器的close方法时,本质上是调用了装饰器内部包裹的流的close方法。比如:
public class TryWithResource { 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中。GZIPOutputStream实际上是FileOutputStream的装饰器。由于try-with-resource的特性,实际编译之后的代码会在后面带上finally代码块,并且在里面调用fin.close()方法和out.close()方法。我们再来看GZIPOutputStream类的close方法:
public void close() throws IOException { if (!closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; }}
我们可以看到,out变量实际上代表的是被装饰的FileOutputStream类。在调用out变量的close方法之前,GZIPOutputStream还做了finish操作,该操作还会继续往FileOutputStream中写压缩信息,此时如果出现异常,则会out.close()方法被略过,然而这个才是最底层的资源关闭方法。正确的做法是应该在try-with-resource中单独声明最底层的资源,保证对应的close方法一定能够被调用。在刚才的例子中,我们需要单独声明每个FileInputStream以及FileOutputStream:
public class TryWithResource { 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(); } }}
由于编译器会自动生成fout.close()的代码,这样肯定能够保证真正的流被关闭。
总结:同时打开多个资源时,单独声明每个资源!
注意
- 被自动关闭的资源必须实现Closeable或AutoCloseable接口。(Closeable是AutoCloseable的子接口,Closeable接口里的close()方法声明抛出了IOException,;AutoCloseable接口里的close()方法声明抛出了Exception)
- 被关闭的资源必须放在try语句后的圆括号中声明、初始化。如果程序有需要自动关闭资源的try语句后可以带多个catch块和一个finally块。
- Java7几乎把所有的“资源类”(包括文件IO的各种类,JDBC编程的Connection、Statement等接口……)进行了改写,改写后的资源类都实现了AutoCloseable或Closeable接口
AutoCloseable 源码:
package java.lang;public interface AutoCloseable { void close() throws Exception;}
Closeable源码:
package java.lang;public interface Closeable implements AutoCloseable { void close() throws IOException;}
若我们在try中打开的资源类没有实现AutoCloseable接口或者Closeable接口,则会报以下错误:
The resource type Connection does not implement java.lang.AutoCloseable
捕获多个异常
当我们操作一个对象的时候,它有时候会抛出多个异常,例如:
public static void try6() { try { Thread.sleep(20000); FileInputStream fis = new FileInputStream("/a/b,txt"); } catch (InterruptedException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } }
这样写起来很繁琐,java7优化后代码如下:
public static void try7() { try { Thread.sleep(20000); FileInputStream fis = new FileInputStream("/a/b,txt"); } catch (InterruptedException | FileNotFoundException e) { e.printStackTrace(); } }
并且catch语句后面的异常参数是final的,不可以再修改
处理反射异常
java7之前使用反射时会有许多需要处理的异常,代码如下:
public static void try7() { Object object = null; Class<?> clazz; try { clazz = Class.forName("tryTest.Connection"); clazz.getMethods()[0].invoke(object); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } }
尽管可以使用上面提到的捕获多个异常技术进行改良,
public static void try7() { Object object = null; Class<?> clazz; try { clazz = Class.forName("tryTest.Connection"); clazz.getMethods()[0].invoke(object); } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) { e.printStackTrace(); } }
但还是十分繁琐,java7引入了新的ReflectiveOperationException 异常类,帮助我们捕获反射异常
public static void try7() { Object object = null; Class<?> clazz; try { clazz = Class.forName("tryTest.Connection"); clazz.getMethods()[0].invoke(object); } catch (ReflectiveOperationException e) { e.printStackTrace(); } }
- Java新特性-try-with-resource
- java新特性之try-with-resources
- Java-try-with-resource
- java中try-with-resource
- Java SE7新特性之try-with-resources语句
- Java SE7新特性之try-with-resources语句
- Java SE7新特性之try-with-resources语句
- Java SE7新特性之try-with-resources语句
- Java SE7新特性之try-with-resources语句
- Java 1.7新特性之(try-with-resources)
- Java SE7新特性之try-with-resources语句
- Java SE7新特性之try-with-resources语句
- java 7新特性-TWR(Try-with-resources)
- Java SE7新特性之try-with-resources语句
- Java 7 新特性try-with-resources语句
- Java SE7新特性之try-with-resources语句
- Java新特性之try-with-resources语句
- Java新特性之try-with-resources实践
- 51Nod 1264:线段相交(计算几何)
- ZooKeeper之(二)数据模型
- webView怎么保存设置Cookie
- Java JVM(七):Function,Consumer,Predicate 接口
- Android jni 静态注册 和动态注册
- Java新特性-try-with-resource
- 职业设计的思考与反思
- var目录权限修改导致ssh无法登录
- AndroidStudio2.2中NDK开发之CMake和传统 JNI在目录结构和配置文件上的区别
- 4399公司2017秋招前端笔试试卷
- fatal: Couldn't find remote ref master
- button等form表单点击出现边框解
- HTML CSS
- C#的bitMap类.bmp图像 8位转24位 和 24位转8位