目录
3.4 try-with-resources语句中声明一个或多个资源
一、资源关闭背景
我们知道,在Java编程过程中,如果打开了外部资源(文件、数据库连接、网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们。
因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制,如果我们不在编程时确保在正确的时机关闭外部资源,就会导致外部资源泄露,紧接着就会出现文件被异常占用,数据库连接过多导致连接池溢出等诸多很严重的问题。
二、JDK7之前的资源关闭方式
为了确保外部资源一定要被关闭,通常关闭代码被写入finally代码块中,当然我们还必须注意到关闭资源时可能抛出的异常,于是变有了下面的经典代码:
public static void main(String[] args) { FileInputStream inputStream = null; try { inputStream = new FileInputStream(new File("test")); System.out.println(inputStream.read()); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } } } }
熟悉其他语言的朋友可能会开始吐槽了,在C++中,我们可以把关闭资源的代码放在析构函数中,在C#中,我们有using代码块。这些语法都有一个共同的特性,让外部资源的关闭行为与外部资源的句柄对象的生命周期关联,当外部资源的句柄对象生命周期终结时(例如句柄对象已出作用域),外部资源的关闭行为将被自动调用。这样不仅更加符合面向对象的编程理念(将关闭外部资源的行为内聚在外部资源的句柄对象中),也让代码更加简洁易懂。怎么到了Java这里,就找不到自动关闭外部资源的语法特性了呢。
三、JDK7及其之后的资源关闭方式
3.1 try-with-resource语法
确实,在JDK7以前,Java没有自动关闭外部资源的语法特性,直到JDK7中新增了try-with-resource语法,才实现了这一功能。
那什么是try-with-resource呢?
简而言之,当一个外部资源的句柄对象(比如FileInputStream对象)实现了AutoCloseable接口,
那么就可以将上面的板式代码简化为如下形式:
public static void main(String[] args) { try (FileInputStream inputStream = new FileInputStream(new File("test"))) { System.out.println(inputStream.read()); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } }
将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。代码是不是瞬间简洁许多!
3.2 实现原理
try-with-resource并不是JVM虚拟机的新增功能,只是JDK实现了一个语法糖,当你将上面代码反编译后会发现,其实对JVM虚拟机而言,它看到的依然是之前的写法:
public static void main(String[] args) { try { FileInputStream inputStream = new FileInputStream(new File("test")); Throwable var2 = null; try { System.out.println(inputStream.read()); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (inputStream != null) { if (var2 != null) { try { inputStream.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { inputStream.close(); } } } } catch (IOException var14) { throw new RuntimeException(var14.getMessage(), var14); } }
3.3 异常抑制
通过反编译的代码,大家可能注意到代码中有一处对异常的特殊处理:
var2.addSuppressed(var11);
这是try-with-resource语法涉及的另外一个知识点,叫做异常抑制。当对外部资源进行处理(例如读或写)时,如果遭遇了异常,且在随后的关闭外部资源过程中,又遭遇了异常,那么你catch到的将会是对外部资源进行处理时遭遇的异常,关闭资源时遭遇的异常将被“抑制”但不是丢弃,通过异常的getSuppressed方法,可以提取出被抑制的异常。
3.4 try
-with-resources语句中声明一个或多个资源
您可以在try
-with-resources语句中声明一个或多个资源。以下示例检索zip文件中打包的文件的名称,zipFileName
并创建包含这些文件名称的文本文件:
public static void writeToFileZipFileContents(String zipFileName, String outputFileName) throws java.io.IOException { java.nio.charset.Charset charset = java.nio.charset.StandardCharsets.US_ASCII; java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName); // Open zip file and create output file with // try-with-resources statement try ( java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) ) { // Enumerate each entry for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) { // Get the entry name and write it to the output file String newLine = System.getProperty("line.separator"); String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine; writer.write(zipEntryName, 0, zipEntryName.length()); } } }
In this example, the
try
-with-resources statement contains two declarations that are separated by a semicolon:ZipFile
andBufferedWriter
. When the block of code that directly follows it terminates, either normally or because of an exception, theclose
methods of theBufferedWriter
andZipFile
objects are automatically called in this order. Note that theclose
methods of resources are called in the opposite order of their creation.在此示例中,
try
-with-resources语句包含以分号分隔的两个声明:ZipFile
和BufferedWriter
。当直接跟随它的代码块正常或由于异常而终止时,将按
BufferedWriter
和ZipFile
对象的close
方法顺序自动调用。请注意,资源的关闭方法按其创建的相反顺序调用。
以下示例使用try
-with-resources语句自动关闭java.sql.Statement
对象:
public static void viewTable(Connection con) throws SQLException { String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement()) { ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); System.out.println(coffeeName + ", " + supplierID + ", " + price + ", " + sales + ", " + total); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } }
java.sql.Statement
此示例中使用的资源是JDBC 4.1及更高版本API的一部分。
注意:
A
try
-with-resources语句可以像普通try
语句一样拥有catch
和finally语块
。在
try
-with-resources语句中,任何catch
或finally
块在声明的资源关闭后运行。
四、文件读取工具类
import java.io.*; /** * @Title: 文件读取 * @ClassName: com.ruoyi.vr.util.FileUtils.java * @Description: * * @Copyright 2020-2021 - Powered By 研发中心 * @author: 王延飞 * @date: 2020/1/24 17:28 * @version V1.0 */ public class FileUtils { /** * 将文本文件中的内容读入到String中 * @param buffer buffer * @param filePath 文件路径 * @throws IOException 异常 * @date 2020-1-7 */ public static String readToBuffer(StringBuffer buffer, String filePath) throws IOException { InputStream is = new FileInputStream(filePath); String line; // 用来保存每行读取的内容 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); line = reader.readLine(); // 读取第一行 while (line != null) { // 如果 line 为空说明读完了 buffer.append(line); // 将读到的内容添加到 buffer 中 buffer.append("\n"); // 添加换行符 line = reader.readLine(); // 读取下一行 } reader.close(); is.close(); return buffer.toString(); } /** * @Title: 将文本文件中的内容读入到String中 <JDK8自动地关闭资源> * @MethodName: readToBufferJDK8 * @param buffer * @param filePath * @Return void * @Exception * @Description: https://blog.csdn.net/fly910905/article/details/86093723 * * @author: 王延飞 * @date: 2020/10/24 17:40 */ public static String readToBufferJDK8(StringBuffer buffer, String filePath) throws IOException { String line; // 用来保存每行读取的内容 File file = new File(filePath); if (file.exists()) { try (InputStream is = new FileInputStream(filePath); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); ) { while ((line = reader.readLine()) != null) { // 读取行,如果 line 为空说明读完了 buffer.append(line); // 将读到的内容添加到 buffer 中 } } catch (IOException e) { return null; } } return buffer.toString(); } public static void main(String[] args) throws IOException { StringBuffer sb = new StringBuffer(); String s = FileUtils.readToBufferJDK8(sb, "C:\\Users\\FLY\\Desktop\\线下实训\\test.html"); System.out.println(s); } }
五、总结
1、当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法更优雅的关闭资源,消除板式代码。
2、try-with-resource时,如果对外部资源的处理和对外部资源的关闭均遭遇了异常,“关闭异常”将被抑制,“处理异常”将被抛出,但“关闭异常”并没有丢失,而是存放在“处理异常”的被抑制的异常列表中。
参考链接:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html