Java : FileクラスよりもPathとFilesを使おう

Fileクラスには API仕様としていくつかの問題点があります。
本記事では、Fileクラスの問題点と、それに代わる API である Pathインタフェース、Filesクラスの利点についてご紹介します。


はじめに

Fileクラスは Java 1 から存在する伝統的なクラスです。
古くから Java を使っていたかたはお世話になったのではないでしょうか。

そして、Java 7 で PathインタフェースFilesクラス という新しいAPIが java.nio.fileパッケージに追加されました。
これらは File に代わる新しいAPIとなります。

特に理由がなければ、File ではなく Path と Files を使うことをおすすめします。

Fileクラスの問題点

java.nio.fileパッケージは、ファイル、ファイル属性、およびファイル・システムにアクセスするためのJava仮想マシン用のインタフェースとクラスを定義します。 このAPIは、java.io.Fileクラスの多くの制限を克服するために使用できます。

File の API仕様では、

  • Fileクラスの多くの制限を克服するために java.nio.fileパッケージ (つまり Path と Files など) を使うことができます。

と紹介されています。


・多くのメソッドで、失敗時に例外がスローされないため、有用なエラー・メッセージを取得できない。 たとえば、ファイルの削除が失敗した場合に、プログラムでは"削除が失敗したこと"は認識できるが、その理由としてファイルが存在しないのか、ユーザーに権限がないのか、または他の問題があるのかが分からない。
・renameメソッドの動作が、プラットフォーム間で一貫していない。

さらに、Java チュートリアルでは Fileクラスの問題点がより具体的に記述されています。
問題点の一部を引用しています。詳細はリンク先をご確認ください。

それでは実際にコード例でも確認してみましょう。
最初に File の例です。

// --- PowerShell ---
//PS R:\java-work> tree /F
//
//R:.
//├─empty-dir
//└─non-empty-dir
//        aaa.txt

final var emptyDir = new File("R:\\java-work\\empty-dir");
System.out.println(emptyDir.delete()); // true

final var nonEmptyDir = new File("R:\\java-work\\non-empty-dir");
System.out.println(nonEmptyDir.delete()); // false

final var nonExistentDir = new File("R:\\java-work\\XXX");
System.out.println(nonExistentDir.delete()); // false

// --- PowerShell ---
//PS R:\java-work> tree /F
//
//R:.
//└─non-empty-dir
//        aaa.txt

File.delete は処理に失敗すると、戻り値で false を返します。
確かに2つめと3つめがなぜ失敗したのか分かりにくいですね。

次に Path と Files の例です。

// --- PowerShell ---
//PS R:\java-work> tree /F
//
//R:.
//├─empty-dir
//└─non-empty-dir
//        aaa.txt

//----------
// OK
final var emptyDir = Path.of("R:", "java-work", "empty-dir");
Files.delete(emptyDir);

//----------
// NG
try {
    final var nonEmptyDir = Path.of("R:", "java-work", "non-empty-dir");
    Files.delete(nonEmptyDir);
} catch (IOException e) {

    System.out.println(e);
}

// 結果
// ↓
// java.nio.file.DirectoryNotEmptyException: R:\java-work\non-empty-dir

//----------
// NG
try {
    final var nonExistentDir = Path.of("R:", "java-work", "XXX");
    Files.delete(nonExistentDir);
} catch (IOException e) {
    System.out.println(e);
}

// 結果
// ↓
// java.nio.file.NoSuchFileException: R:\java-work\XXX

// --- PowerShell ---
//PS R:\java-work> tree /F
//
//R:.
//└─non-empty-dir
//        aaa.txt

Files では処理に失敗すると、IOException(もしくはそのサブクラス)を発生させます。
例では DirectoryNotEmptyException と NoSuchFileException です。

例外の名前を見ただけで原因が推測できるので、問題解析は Fileクラスの true/false よりやりやすいですね。

あとは本サイトの記事「例外 vs. 戻り値でエラーコード」でも紹介させていただきましたが、基本的には戻り値より例外のほうが堅牢なエラー処理ができるのでおすすめです。


Pathインタフェース、Filesクラスの利点

ファイル・システム内のファイルを特定するために使用可能なオブジェクトです。

このクラスは、ファイル、ディレクトリ、またはその他の種類のファイルを操作するstaticメソッドだけで構成されます。

Fileクラスの問題点で紹介した内容が、Path、Files では解消されます。
それ以外にも利点があります。

それぞれのAPIの役割を見てましょう。

API 役割
File ・パス名を保持する。
・パス名の操作。(例えば getParent で親のパス名を取得)
・実際のファイルやディレクトリにアクセス。(mkdir, exists, deleteなど)
Path ・パス名を保持する。
・パス名の操作。(例えば getParent で親のパス名を取得)
Files ・実際のファイルやディレクトリにアクセス。(createDirectory, exists, deleteなど)

Fileクラスの持っていた役割を、Pathインタフェースと Filesクラスの2つに分割したイメージですね。

実際のファイルへのアクセスは、プログラムのなかでも慎重に扱わないといけないものの1つです。
間違ってユーザの大切なファイルを消してしまったら大変です…

Fileクラスを使うと、パス名の操作しか必要としないコードでも、本当にファイルにアクセスしていないか?と不安になるかもしれません。
コードレビューなどではより慎重に確認する必要があるでしょう。

しかし、本当にパス名だけの操作であれば Path だけを使えば安心できます。
なぜなら Files が出てこない限りファイルへのアクセスはしていないからです。

補足

  • Path には toRealPath, register など一部 IOExceptionが発生する(ファイルにアクセスする)メソッドがあります。

相互運用も可能

とはいえ、古いAPIには Fileクラスをパラメータに要求するものがあるかもしれません。
そんなときも大丈夫です。

File ←→ Path

の相互変換用のAPIも用意されています。

final Path path = Path.of("sample.txt");

final File file = path.toFile();
System.out.println(file); // sample.txt
final File file = new File("sample.txt");

final Path path = file.toPath();
System.out.println(path); // sample.txt

まとめ

Fileクラスは古いAPIです。
特に理由がなければ、新しいAPIである PathFiles を積極的に使うことをおすすめします。


関連記事

ページの先頭へ