java program idiom之 资源初始化与清理

源的初始化与关闭是非常常见的操作,也是很容易出错的地方。Java里一般使用try-catch-finally来处理这个问题,在JDK7增加了try-with-resource。

1.1.  try-catch-finally

下面是个有错误的举例:

public void copy_error(File src, File dst) throws IOException {
    FileInputStream fin = new FileInputStream(src);
    FileOutputStream fout = new FileOutputStream(dst);
    try {
        // do copy
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        fin.close();
        fout.close();
    }
}

 
上面的代码存在的问题有:

A、 fin、 fout 的初始化没有放入 try - catch 语句,如果 fin 初始化成功,而 fout 初始化时抛出异常,则会导致 fin 不会关闭,因为此时还没有进入 try 语句块,所以 finally 块是不会执行的。

B、 在finally 中的 fin 、 fout 的关闭错误。如果 fin.close() 执行时抛出异常,则不会执行 fout.close 。正确的做法是,由于 Input/OutputStream 实现了 Closeable 接口,可以同等对待,借助可变参数的特点,可以实现这样一个工具方法:( IoUtils.close )

public static void close(Closeable... closeables) {
    for (Closeable closeable : closeables) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception ignore) {
            }
        }
    }
}

 
关闭流时只需调用IoUtils.close(fin, fout); ,就算只处理单一的输入输出流,这个方法也可用,因为它的参数是可变的。正确的代码大致如下:

public void copy_better(File src, File dst) throws FileNotFoundException {
    FileInputStream fin = null;
    FileOutputStream fout = null;
    try {
        fin = new FileInputStream(src);
        fout = new FileOutputStream(dst);
        // do copy
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        IoUtils.close(fin, fout);
    }
}

 
涉及SQL 操作也可以先实现一个工具方法:

public static void close(ResultSet rs, Statement stmt, Connection conn) {
    if (rs != null) {
        try {
            rs.close();
        } catch (Exception e) {
        }
    }

    if (stmt != null) {
        try {
            stmt.close();
        } catch (Exception e) {
        }
    }

    if (conn != null) {
        try {
            conn.close();
        } catch (Exception e) {
        }
    }
}

 
代码模板大致是这样:

public void doSql() {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try {
        // conn = ...;
        // stmt = ...;
       
        // rs = stmt.executeQuery("");
        // do sql operation
    } finally {
        Utils.close(rs, stmt, conn);
    }
}

 
所以在传统的try-catch-finally 执行资源有关的操作的模板是:

1) 、在try 语句块 前 声明变量;

2) 、在try 语句块 内 初始化变量;

3) 、在finally 语句块中执行关闭操作,且必须确保每个资源的关闭操作得到执行。

1.2.  JDK7 try-with-resources

JDK7中提供了 AutoCloseable 接口和 try-with-resource( 也称为 ARM (Automatic Resource Management)

)来减少资源泄露的情况。 下面以SQL 操作为例:

try (Connection conn = DriverManager.getConnection("url");
        Statement stmt = conn.createStatement()) {
    // do sql operation
} catch (SQLException e) {
    e.printStackTrace();
}

 

我们不需要对在try() 里声明的实现了 AutoCloseable 的资源类进行显式的关闭,新的特性可以确保try() 的小括号里 声明 的资源得到关闭。

但有可能误用而导致资源泄露,下面是个示例:

try  (BufferedReader br =  new  BufferedReader( new  FileReader(path))) {
     br.readLine();
}

 

问题出在: new  BufferedReader( new  FileReader(path)) ,如果 FileReader 构建成功,但在构建 BufferedReader 出错,则 br是 null ,不会执行关闭操作;而构建出来的 FileReader 只是 BufferedReader 构造函数的一个参数,ARM 不会对它执行关闭,从而导致资源泄露。避免这种 bug 的做法可以这样:

try  (FileReader reader =  new  FileReader(path)) {

     BufferedReader br =  new  BufferedReader(reader);

     br.readLine();

}

 

其实这个陷阱在传统的 try-catch-finally 里也存在,只是在 ARM 里更容易误解,因为 ARM 号称自动管理资源。

这个 陷阱 在使用装饰者模式的资源有关的类库里应该容易出现。 JDK 的 io 库在设计时使用了装饰者模式,这样可以动态地扩展功能,比如 InputStream ,基础的字节输入流,被 BufferedInputStream 包装后,又具有了缓冲的功能,再被 DataInputStream 包装后,具有了读取 Java基本数据类型的能力 。 DataInputStream 、 BufferedInputStream 称为装饰者,它们都实现了被装饰者 InputStream的接口。因为它们在类型上一致性,所以可能这样构建一个资源:

new  DataInputStream( new   BufferedInputStream ( new  FileInputStream(path)));

从而导致上面的 陷阱 。

对任何使用装饰者模式实现的与资源有关的类库都应该留心这个 陷阱 。

关于这个 陷阱 还可以查看这篇博客:

http://java.dzone.com/articles/common-try-resources-idiom

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值