Java : FileクラスよりもPathとFilesを使おう
本記事では、Fileクラスの問題点と、Java 7から追加された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など)が紹介されています。
さらに、チュートリアルではFileクラスの問題点が記述されています。
・多くのメソッドで、失敗時に例外がスローされないため、有用なエラー・メッセージを取得できない。 たとえば、ファイルの削除が失敗した場合に、プログラムでは"削除が失敗したこと"は認識できるが、その理由としてファイルが存在しないのか、ユーザーに権限がないのか、または他の問題があるのかが分からない。
・renameメソッドの動作が、プラットフォーム間で一貫していない。
問題点の一部を引用しています。詳細はリンク先をご確認ください。
それでは実際にコード例でも確認してみましょう。
最初に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\\XXXXXX");
System.out.println(nonExistentDir.delete()); // false
File.deleteは処理に失敗すると、戻り値でfalseを返します。
確かに2つめと3つめがなぜ失敗したのか分かりにくいですね。
次にPathとFilesの例です。
// --- PowerShell ---
//PS R:\java-work> tree /F
//
//R:.
//├─empty-dir
//└─non-empty-dir
// aaa.txt
final var emptyDir = Path.of("R:", "java-work", "empty-dir");
Files.delete(emptyDir);
final var nonEmptyDir = Path.of("R:", "java-work", "non-empty-dir");
Files.delete(nonEmptyDir); // DirectoryNotEmptyException発生
final var nonExistentDir = Path.of("R:", "java-work", "XXXXXX");
Files.delete(nonExistentDir); // NoSuchFileException発生
Filesでは処理に失敗すると、IOException(もしくはそのサブクラス)を発生させます。
例ではDirectoryNotEmptyExceptionとNoSuchFileExceptionです。
例外の名前を見ただけで原因が推測できるので、問題解析はFileクラスのtrue/falseよりやりやすいですね。
あとは本サイトの記事「Java TIPS : 例外 vs. 戻り値でエラーコード」でも紹介させていただきましたが、基本的には戻り値より例外のほうが堅牢なエラー処理ができるのでおすすめです。
Pathインタフェース、Filesクラスの利点
ファイル・システム内のファイルを特定するために使用可能なオブジェクトです。
このクラスは、ファイル、ディレクトリ、またはその他の種類のファイルを操作するstaticメソッドだけで構成されます。
Fileクラスの問題点で紹介した内容が、Path、Filesでは解消されます。
それ以外にも利点があります。
それぞれのAPIの役割を見てましょう。
API | 役割 |
---|---|
File | ・パス名を保持する。 ・パス名の操作。(例えばgetParentで親のパス名を取得) ・実際のファイルやディレクトリにアクセス。(mkdir, exists, deleteなど) |
Path | ・パス名を保持する。 ・パス名の操作。(例えばgetParentで親のパス名を取得) |
Files | ・実際のファイルやディレクトリにアクセス。(createDirectory, exists, deleteなど) |
Fileの持っていた役割を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であるPath, Filesを積極的に使うことをおすすめします。
関連記事も参考にしていただけたら幸いです。