自动关闭文件

在前面的小节中,示例程序在不需要文件时,显式调用了close()来关闭文件,从Java第一次创建以后,就开始以这种方法关闭文件.所以,现在的代码中广泛使用这种方法.这种方法仍然有效,也很有用.但是,从JDK7开始,Java新增了一种功能,通过自动化关闭资源的过程为管理资源(例如文件流)提供了另外一种更加简化的方式.这种功能的基础是一种新形式的try语句,叫做try-with-resources,有时候称为自动资源管理.try-with-resource的主要优势在于避免了当不再需要文件(或其他资源)时忘记关闭文件的情况.前面已经解释过,忘记关闭文件可能会导致内存泄漏,并引起其他问题.
try-with-resource语句的基本形式如下:

try(resource-specification)
{
    //use the resource
}
这里,resource-specification是一条声明并初始化资源(例如文件)的语句.他包含一个变量声明,该变量的初始化是通过引用被管理对象来实现的.当try块结束时,资源会自动释放.就文件而言,这意味着文件将被自动关闭,因此不需要显式调用close().try-with-resource语句也可以包含catch和finally语句.
try-with-resources语句只能用于实现java.lang定义的autoCloseable接口的那些资源.这个接口定义了close()方法.Java.io中的Closeable接口继承了AutoCloseable.两个接口都被流类实现,包括FileInputStream和FileOutputStream.因此,在使用流(包括文件流)时,可以使用try-with-resources.
作为自动关闭文件的第一个示例,下面的程序对ShowFile程序做了修改,以使用try-with-resources:
    public static void main(String args[])
    throws IOException
    {
        int i;
        String filePath = "test.txt";
        
        try (FileInputStream fin = new  FileInputStream(filePath))
        {
            do {
                i = fin.read();
                if(i!=-1)  System.out.print((char)i);
            } while(i != -1);
        } catch(FileNotFoundException exc) {
            System.out.println(exc.toString());
        } catch(IOException exc) {
            System.out.println("Error Reading  file");
        }
        System.out.println("done");
    }
在程序中,要特别注意try-with-resources语句打开文件的方式:
try(FileInputStream fin) = new FileInputStream(filePath)
注意try语句的资源声明部分声明了一个名为fin的FileInputStream,并把由其构造函数打开的文件应用赋值给他.因此,在这个版本的程序中,变量fin是try块局部变量,在进入try时创建.退出try时,与fin关联的文件会由于隐式调用close()而被自动关闭.因为不需要显式调用close(),所以不会发生忘记关闭文件的情况.这是自动资源管理的一个关键优势.
try语句中声明的资源隐式的被指定为final,理解这一点很重要.这意味着在创建资源后不能为他赋值.另外,该资源的作用域被限定为声明他的try-with-resources语句内.
在一条try语句中可以管理多个资源.为此,只需将每个资源声明用分号隔开.下面的程序就是一个例子.他重新编写了前面的CopyFile程序,使其使用一条try-with-resources语句同时管理fin和fout:
    public static void main(String args[])
    throws IOException
    {
     int i;
        String filePath = "test.txt";
        String filePath2 = "test_copy.txt";
        
        try (FileInputStream fin = new  FileInputStream(filePath);
            FileOutputStream fout = new  FileOutputStream(filePath2);)
        {
            do {
                i = fin.read();
                if(i!=-1)
                {
                    fout.write(i);
                    System.out.print((char)i);
                }
            } while(i != -1);
        } catch(FileNotFoundException exc) {
            System.out.println(exc.toString());
        } catch(IOException exc) {
            System.out.println("Error Reading  file");
        }
        System.out.println("done");
    }
在这个程序中,注意在try中打开输入和输出文件的方式:
try (FileInputStream fin = new  FileInputStream(filePath);
      FileOutputStream fout = new  FileOutputStream(filePath2))
在这个try块结束后,fin和fout都会被关闭.比较两个版本的程序会发现,这个版本的程序更加简短.能够简化源代码是try-with-resources带来的另一个好处.
try-with-resources还有一个需要解释的方面,一般来说,try块执行时,有可能发生这样的情况:当finally语句中的资源关闭时,try块中的一个异常可能会引起另一个异常.如果是”普通的”try语句,原来的异常会被第二个异常取代,从而丢失.但是,在try-with-resources语句中,第二个异常将被抑制.但是他不会丢失,而是被添加到与第一个异常相关的被抑制异常的列表中.通过使用throwable定义的getSuppressed()方法可以获得被抑制异常的列表.
由于try-with-resources存在这么多优势,因此后面的示例都会使用他,但是,熟悉传统的显示调用close()的方法仍然十分重要.这有几个原因.首先,现在仍然有大量遗留代码依赖于传统的方法.所有的Java程序员都应该完全了解和熟悉这种传统方法,以便可以维护和更新原来的代码.其次,在某些时候,可能需要工作在不能使用JDK7的环境中.此时,将无法使用try-with-resources语句,所以必须使用传统的方法.最后,在有些情况下,显式关闭资源可能比自动关闭资源更加合适.虽然如此,但如果正在使用的是JDK7,JDK8或更高版本,通常应该使用更新的自动化方法来管理资源.与传统方法相比,这种方法更简洁,更健壮.