Java7 で File クラスもリファクタリングされていました

先日の記事(Java7 で String クラスがリファクタリングされていました - 地平線に行く)に続いて、File クラスを調べてみました。

前回ほどの変更はありませんでしたが、それでも着実にソースがよくなっていました。

listFiles メソッド - 新文法の積極活用

Java6では、新しい文法(ジェネリクスなど)を使わないソースが残っていました。

// (Java6) Fileクラス、1128行目〜
public File[] listFiles(FileFilter filter) {
    (…中略…)
    ArrayList v = new ArrayList();    // ジェネリクスを使っていない
    for (int i = 0 ; i < ss.length ; i++) {    // 拡張 for 文を使っていない
        File f = new File(ss[i], this);
        if ((filter == null) || filter.accept(f)) {
            v.add(f);
        }
    }
    return (File[])(v.toArray(new File[v.size()]));
}

それが、Java7では一新され、今回追加になった文法(ダイアモンドオペレータ)までを積極的に使うようになっていました。

// (Java7) Fileクラス、1211行目〜
public File[] listFiles(FileFilter filter) {
    (…中略…)
    ArrayList<File> files = new ArrayList<>();    // ダイアモンドオペレータを使ってる!
    for (String s : ss) {
        File f = new File(s, this);
        if ((filter == null) || filter.accept(f))
            files.add(f);
    }
    return files.toArray(new File[files.size()]);
}

Javaのいいところの一つに、「JavaAPIは、Javaのサンプルコードになっている」というのがあります*1。積極的に新しい文法の手本を示したこのリファクタリングも、とてもいいサンプルだと思います。

createTempFile メソッド - 例外の変更

一時ファイルの作成 - File#createTempFile(〜)がセキュリティポリシーによって拒否された場合、 SecurityException が発生します。このとき、Java6では「一時フォルダの場所を明かさないようにするため」詳細を常に非開示にしていました。
しかし、Java7ではこの制限が緩められ、「一時ファイルを生成するディレクトリを自分で指定した場合は、詳細を開示する」ように変更されていました。

// (Java6) Fileクラス、1695行目〜
    try {
        sm.checkWrite(filename);
    } catch (AccessControlException x) {
        /* Throwing the original AccessControlException could disclose
           the location of the default temporary directory, so we
           re-throw a more innocuous SecurityException */
        throw new SecurityException("Unable to create temporary file");
    }
// (Java7) Fileクラス、1869行目
    try {
        sm.checkWrite(f.getPath());
    } catch (SecurityException se) {
        // don't reveal temporary directory location
        if (directory == null)
            throw new SecurityException("Unable to create temporary file");
        throw se;
    }


試しにテストしてみたところ、確かに結果が異なりました*2
これなら、セキュリティポリシー関連でトラブルが起きた時でも解析がやりやすそうです。

toPath - 効率的な変換

リファクタリングとはちょっと違うんですが)File クラスと Path インタフェースを変換するメソッド - File#toPath() が追加されていました。
その実装を確認してみたところ、一度呼び出されたら内部でその値を保持するようになっていました。*3
そのため、わざわざ Path を自前で保持せずに、必要なときに何度でも toPath() を呼べばいいようです。

private volatile transient Path filePath;

public Path toPath() {
    Path result = filePath;
    if (result == null) {
        synchronized (this) {
            result = filePath;
            if (result == null) {
                result = FileSystems.getDefault().getPath(path);
                filePath = result;
            }
        }
    }
    return result;
}

感想

String クラスほどの変化はありませんでしたが、それでも着実によくなっていると感じました。
Java は 6 で止まったかのようにも見えましたが、こういう風に改良が進められているのを見ると、まだまだ進化していくんだ! と安心しました。


このほかにも、細かい変化がたくさんあったので、ちょくちょく調べていこうと思います。

createTempFile のテストに使ったプログラム

public class FileTest {
    @Test
    public void testCreateTempFile() throws IOException{
        // 事前に ${user.dir}/java.policy を作成し、ファイルの作成を拒否しておく
        // ------------------------------------------------
        // grant {
        //    permission java.io.FilePermission "r:\\-", "read";
        // };
        System.setSecurityManager(new SecurityManager());
        File.createTempFile("TEST", null, new File("r:\\test"));
    }
}

*1:個人的な持論です・・・。

*2:Java6 は「 java.lang.SecurityException: Unable to create temporary file 」だけでしたが、Java7 は「 java.security.AccessControlException: access denied ("java.io.FilePermission" "r:\test\TEST9057213546061933835.tmp" "write") 」と詳細が表示されました。テストコードは、「続きを読む」に記載しています。

*3:典型的な二重チェックイデオムです。ちなみに、File クラスは遅延初期化ホルダークラスイデオムも使われていて、Effective Java - 項目71(遅延初期化を注意して使用する)の実践例のようになっていました。