目录
发展历程:
1.JDK7之前资源需要手动关闭
在JDK7之前,必须要在finally中执行关闭资源的方法,所谓的资源(resource)是指在程序完成后,必须关闭的对象,否则随着程序不断运行,资源泄露将会累计成重大的生产事故,如果你同时打开多个资源不执行关闭操作,随着程序不断运行将会出现噩梦般的场景。
jdk7之前必须要手动在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) {
e.printStackTrace();
}
finally {
if (bout != null) {
try {
bout.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
我们从代码中可以看出,finally中关闭资源的代码比业务代码还要多,这是因为我们不仅需要关闭BufferedInputStream资源,还要确保如果在关闭BufferedInputStream的过程中出现了异常, BufferedOutputStream也要能被正常关闭。所以我们不得不借助finally中嵌套finally的方法,保证资源最终能被关闭。可想而知打开的资源越多,finally中嵌套的将会越深,违背了代码的可维护性和可读性原则;
2.JDK7使用try-with-resources语法糖
try-with-resources 是 JDK 7 中一个新的异常处理机制,实现了 java.lang.AutoCloseable 接口,其中实现了 java.io.Closeable 的所有对象;通过try()的方式来打开资源,不需要我们自己书写关闭资源的代码;通过try-with-resource语法糖声明的资源对象在执行完方法后会自动关闭;
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();
}
}
}
try-with-resource关闭多个资源的规则:
1.实现了AutoCloseable接口的类,在try()里声明了该类实例,在try结束后close方法都会被调用;
2.try结束后自动调用的close方法,这个动作会早于finally里调用的方法;
3.不管是否出现异常(int i=1/0会抛出异常),try()里的实例都会被调用close方法;
4.越晚声明的对象,会越早被close掉;
关闭多个资源的实例代码:
private static void testAutoClose() {
AutoCloseable global_obj1 = null;
AutoCloseable global_obj2 = null;
try(AutoCloseable obj1 = new AutoClosedImpl("obj1");
AutoCloseable obj2 = new AutoClosedImpl("obj2");){
global_obj1= obj1;
int i = 1/0;
global_obj2= obj2;
} catch (Exception e) {
e.printStackTrace();
}finally{
try{
System.out.println("before finally close");
if(global_obj1!=null){
global_obj1.close();
}
if(global_obj2!=null){
global_obj2.close();
}
System.out.println("after finally close");
} catch(Exception e){
e.printStackTrace();
}
}
}
private static class AutoClosedImpl implements AutoCloseable{
private String name;
public AutoClosedImpl(String name){
this.name = name;
}
@Override
public void close() throws Exception {
System.out.println(name+" closing");
}
}
控制台输出如下:
obj2 closing
obj1 closing
before finally close
obj1 closing
after finally close
java.lang.ArithmeticException: / by zero
at trywithresources.AutoCloseTest.testAutoClose(AutoCloseTest.java:60)
at trywithresources.AutoCloseTest.main(AutoCloseTest.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
try-with-resources异常处理机制:
JDK1.7开始,java引入了 try-with-resources 声明,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,在编译时会进行转化为 try-catch-finally 语句。新的声明包含三部分:try-with-resources 声明、try 块、catch 块。它要求在 try-with-resources 声明中定义的变量实现了 AutoCloseable 接口,这样在系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能,它们的异常抛出机制也发生了变化;
1. try-catch-finally结构中,异常处理有两种情况:
catch (Exception e) {
e.printStackTrace(); // 第一处异常处理
}
finally {
try {
if (c != null) {
c.close();
}
} catch (IOException e) {
e.printStackTrace(); // 第二处异常处理
}
}
- 1.try 块没有发生异常时,直接调用finally块,如果 close 发生异常,就处理。
- 2.try 块发生异常,catch 块捕捉,进行第一处异常处理,然后调用 finally 块,如果 close 发生异常,就进行第二处异常处理。
2. try-with-resources 结构中,异常处理两种情况:
- 1.try 块没有发生异常时,自动调用 close 方法,如果发生异常,catch 块捕捉并处理异常,不论 try 中是否有异常,都会首先自动执行 close 方法,然后才判断是否进入 catch 块;
- 2.try 块发生异常,自动调用 close 方法,如果 close 也发生异常,catch 块只会捕捉 try 块抛出的异常,close 方法的异常会在catch 中被压制,你可以在catch块中用 Throwable.getSuppressed 方法来获取到压制异常的信息数组;
public class Main {
public static void startTest() {
try (MyAutoCloseA a = new MyAutoCloseA();
MyAutoCloseB b = new MyAutoCloseB()) {
a.test();
b.test();
} catch (Exception e) {
System.out.println("Main: exception");
System.out.println(e.getMessage());
Throwable[] suppressed = e.getSuppressed();
for (int i = 0; i < suppressed.length; i++)
System.out.println(suppressed[i].getMessage());
}
}
public static void main(String[] args) throws Exception {
startTest();
}
}
/* 输出为:
MyAutoCloaseA: test()
MyAutoCloseB: on close
MyAutoCloseA: on close()
Main: exception
MyAutoCloaseA: test() IOException
MyAutoCloaseB: close() ClassNotFoundException
MyAutoCloaseA: close() ClassNotFoundException
*/
class MyAutoCloseA implements AutoCloseable {
public void test() throws IOException {
System.out.println("MyAutoCloaseA: test()");
throw new IOException("MyAutoCloaseA: test() IOException");
}
@Override
public void close() throws Exception {
System.out.println("MyAutoCloseA: on close()");
throw new ClassNotFoundException("MyAutoCloaseA: close() ClassNotFoundException");
}
}
class MyAutoCloseB implements AutoCloseable {
public void test() throws IOException {
System.out.println("MyAutoCloaseB: test()");
throw new IOException("MyAutoCloaseB: test() IOException");
}
@Override
public void close() throws Exception {
System.out.println("MyAutoCloseB: on close");
throw new ClassNotFoundException("MyAutoCloaseB: close() ClassNotFoundException");
}
}
注意:
- 1.catch 块中,看不到 try-with-recourse 声明中的变量;
- 2.try-with-recourse 中,try 块中抛出的异常,在 e.getMessage() 可以获得,而调用 close() 方法抛出的异常在e.getSuppressed() 获得;
- 3.try-with-recourse 中定义多个变量时,关闭的顺序是从后往前;
3.JDK9中try-with-resources的改进
try-with-resources 中变量的声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中直接使用该变量,而无需在 try-with-resources 语句中声明一个新的变量,来看一下改进前后的代码对比:
改进前,jdk7/jdk8
// A final resource
final Resource resource1 = new Resource("resource1");
// An effectively final resource
Resource resource2 = new Resource("resource2");
try (Resource r1 = resource1;
Resource r2 = resource2) {
// 使用resource1 and resource 2 通过变量r1 and r2.
}
改进后,JDK9中的使用方式
// A final resource
final Resource resource1 = new Resource("resource1");
// An effectively final resource
Resource resource2 = new Resource("resource2");
try (resource1;
resource2) {
// 直接使用 resource1 and resource 2.
}
扩展:
用try-with-resource的形式使用锁
通过封装ReentrantReadWriteLock,来实现用try-with-resource的形式使用锁;
封装类:
import java.io.Closeable;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class AutoReadWriteLock {
public interface AutoLock extends Closeable {
// a close() that don't throw exception
public void close();
}
// ============================================================================
final private ReentrantReadWriteLock rwlock;
public AutoReadWriteLock() {
this(new ReentrantReadWriteLock());
}
public AutoReadWriteLock(ReentrantReadWriteLock rwlock) {
this.rwlock = rwlock;
}
public ReentrantReadWriteLock innerLock() {
return rwlock;
}
public AutoLock lockForRead() {
rwlock.readLock().lock();
return new AutoLock() {
@Override
public void close() {
rwlock.readLock().unlock();
}
};
}
public AutoLock lockForWrite() {
rwlock.writeLock().lock();
return new AutoLock() {
@Override
public void close() {
rwlock.writeLock().unlock();
}
};
}
}
使用类:
public class Test{
private Map<String, String> test = new HashMap<>();
private AutoReadWriteLock testMapLock = new AutoReadWriteLock();
public void put(String str){
try (AutoLock lock = testMapLock.lockForWrite()){
test.put(str,str);
}
}
public String get(String str){
try (AutoLock lock = testMapLock.lockForRead()){
return test.get(str);
}
}
}