Java IO 四大附加接口、try-with-resource
Java IO 四大附加接口、try-with-resource
@author ixenos
四大附加接口 Closeable、Flushable、Readable、Appendable
Closeable:
void close() throws IOException 关闭此流并释放与此流关联的所有系统资源
java.io.closeable扩展了java.lang.AutoCloseable,因此,对任何Closeable进行操作时,都可以使用try-with-resources语句
- try-with-resources 可以声明一个或多个资源,资源之间用分号隔开。
-
1 try ( 2 java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); 3 java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) 4 ) { 5 ... 6 }
有关 try-with-resource 特性的具体分析请见下文。
Flushable:
void flush() 将所有已缓冲输出写入底层流
Readable:
int read(CharBuffer cb) 形参中的CharBuffer类拥有按顺序和随机地进行读写访问的方法,它表示一个内存中的缓冲区或者一个内存映像(虚拟内存)的文件
Appendable:
Appendable append(char c) 添加的单个字符
Appendable append(CharSequence s) 添加字符序列,CharSequence接口描述了一个char值序列的基本属性,String、CharBuffer、StringBuffer、StringBuilder都实现了CharSequence,所以可以传入这些类型的对象
以上两个方法,都返回this
四大IO抽象类与附加接口的关系
1、InputStream、OutputStream、Reader、Writer都实现了Closeable和AutoCloseable接口,因此,都可以使用 try-with-resources 语句
2、OutputStream、Writer实现了Flushable接口
3、Reader实现了Readable接口
4、Writer实现了Appendable接口
try-with-resource
try-with-resource 是Java SE 7 加入AutoCloseable接口后才有的,closeable接口继承了AutoCloseable接口。
AutoCloseable接口规定了自动关闭资源:
The close() method of an AutoCloseable object is called automatically when exiting a try-with-resources block for which the object has been declared in the resource specification header.
1、这个所谓的try-with-resources,是个语法糖。实际上就是自动调用资源的close()函数。
1 public class TryStudy implements AutoCloseable{ 2 static void test() throws Exception { 3 try(TryStudy tryStudy = new TryStudy()){ 4 System.out.println(tryStudy); 5 } 6 } 7 @Override 8 public void close() throws Exception { 9 } 10 }
2、可以在一个 try
-with-resources 语句中声明一个或多个资源(用分号隔开),但要注意资源的 close
方法调用顺序与它们的创建顺序相反
1 try ( 2 java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); 3 java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) 4 )
资源的close方法在JVM中的调用顺序是: writer.close() zf.close()
3、下面从编绎器生成的字节码来分析下,try-with-resources到底是怎样工作的:
TryStudy实现了AutoCloseable接口,下面来看下test函数的字节码:
1 static test()V throws java/lang/Exception 2 TRYCATCHBLOCK L0 L1 L2 3 TRYCATCHBLOCK L3 L4 L4 4 L5 5 LINENUMBER 21 L5 6 ACONST_NULL 7 ASTORE 0 8 ACONST_NULL 9 ASTORE 1 10 L3 11 NEW TryStudy 12 DUP 13 INVOKESPECIAL TryStudy.<init> ()V 14 ASTORE 2 15 L0 16 LINENUMBER 22 L0 17 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 18 ALOAD 2 19 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V 20 L1 21 LINENUMBER 23 L1 22 ALOAD 2 23 IFNULL L6 24 ALOAD 2 25 INVOKEVIRTUAL TryStudy.close ()V 26 GOTO L6 27 L2 28 FRAME FULL [java/lang/Throwable java/lang/Throwable TryStudy] [java/lang/Throwable] 29 ASTORE 0 30 ALOAD 2 31 IFNULL L7 32 ALOAD 2 33 INVOKEVIRTUAL TryStudy.close ()V 34 L7 35 FRAME CHOP 1 36 ALOAD 0 37 ATHROW 38 L4 39 FRAME SAME1 java/lang/Throwable 40 ASTORE 1 41 ALOAD 0 42 IFNONNULL L8 43 ALOAD 1 44 ASTORE 0 45 GOTO L9 46 L8 47 FRAME SAME 48 ALOAD 0 49 ALOAD 1 50 IF_ACMPEQ L9 51 ALOAD 0 52 ALOAD 1 53 INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V 54 L9 55 FRAME SAME 56 ALOAD 0 57 ATHROW 58 L6 59 LINENUMBER 24 L6 60 FRAME CHOP 2 61 RETURN 62 LOCALVARIABLE tryStudy LTryStudy; L0 L7 2 63 MAXSTACK = 2 64 MAXLOCALS = 3
从字节码里可以看出,的确是有判断tryStudy对象是否为null,如果不是null,则调用close函数进行资源回收。
try-with-resource与异常捕获
1、从 被丢弃 到 被抑制 的异常 Suppressed Exceptions
再仔细分析,可以发现有一个Throwable.addSuppressed的调用,那么这个调用是什么呢?
使用了try-catch语句之后(try-with-resource也算是try-catch),有可能会出现两种异常,一个是try块里的异常,一个是调用close函数里抛出的异常。
a) 在JDK1.7 以前,一旦finally块抛出了close函数里的异常,前面try块catch的异常被丢弃了!
b) 而在 try-with-resources 语句中,如果在调用close函数时出现异常(注意这个前提),那么前面的异常就被称为Suppressed Exceptions,因此Throwable还有个addSuppressed函数可以把它们保存起来,当用户捕捉到close里抛出的异常时,就可以调用Throwable.getSuppressed函数来取出close之前的异常了。
2、注意:
一个 try
-with-resources 语句可以像普通的 try
语句那样有 catch
和 finally
块。
但是在try
-with-resources 语句中, 任意的 catch
或者 finally
块都是在声明的资源被关闭以后才运行。
3、catch多种异常,但抛出一种异常时
在JDK1.7之前catch多个异常是这样的:
1 try{ 2 //逻辑代码 3 }catch (IOException ex) { 4 logger.log(ex); 5 throw new SpecialException(); 6 catch (SQLException ex) { 7 logger.log(ex); 8 throw new SpecialException(); 9 }
从上述代码中可以看出这样写非常的难看,并且会出现许多重复的代码。
JDK1.7及以后可以这样:
1 try{ 2 //逻辑代码 3 }catch (IOException | SQLException ex) { 4 logger.log(ex); 5 throw new SpecialException(); 6 }
注:上面例子中的ex是隐式的final不可以在catch块中改变ex。
4、在JDK1.7以前的版本,在方法声明中声明抛出的异常如果在方法体内没有抛出时不被允许的,如下:
1 static class FirstException extends Exception { 2 3 } 4 5 static class SecondException extends Exception { 6 7 } 8 9 public void rethrowException(String exceptionName) throws Exception { 10 11 try { 12 13 if (exceptionName.equals("First")) { 14 //如果异常名称为"First",则抛出异常一 15 throw new FirstException(); 16 17 } else { 18 //否则的话,则抛出异常二 19 throw new SecondException(); 20 21 } 22 23 } catch (Exception e) { 24 25 throw e; 26 } 27 }
JDK1.7及以后版本:
1 static class FirstException extends Exception { 2 3 } 4 5 static class SecondException extends Exception { 6 7 } 8 9 public void rethrowException(String exceptionName) throws Exception, FirstException, SecondException { 10 try { 11 // 逻辑代码 12 }catch (Exception e) { 13 14 throw e; 15 } 16 }