倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

これからの「Java I/O」の話をしようwww (1) : Path インターフェース, Paths クラス

Java nio の API を見ていくシリーズ。

最近 Java SE 7 が正式リリースされたので、遅ればせながら API や言語使用の変更などをチラチラとチェック。 で、とりあえず身に着けておいた方が良さそうなのは I/O 周りのことかなぁと思ったので、API をあれこれ試してみることに。 記事の題名に反して、「仕様の哲学」みたいなことには触れませぬ*1

シリーズ目次

  1. Path インターフェース, Paths クラス
  2. Files クラス 〜概要〜
  3. Files クラスのメソッド 〜ファイル、ディレクトリの作成〜
  4. Files クラスのメソッド 〜ファイル、ディレクトリの操作〜
  5. Files クラスのメソッド 〜ファイル内容の読み書き〜
  6. Files クラスのメソッド 〜ファイル、ディレクトリの属性〜
  7. Files クラスのメソッド 〜続・ファイル、ディレクトリの属性〜
  8. Files クラスのメソッド 〜ディレクトリの内容を走査〜
  9. Files クラスのメソッド 〜ディレクトリの階層を走査〜
  10. Files クラスのメソッド 〜ディレクトリの階層を Stream で走査〜
  11. Files クラスのメソッド ~MIME Type を調べる~
  12. ファイルシステム関連の型 FileSystem, FileSystems, FileStore
  13. Files クラスのメソッド ~続々・ファイル、ディレクトリの属性~

参考 URL

java.nio.file パッケージ

今までの Java バージョンでも既に java.nio パッケージは存在してましたが、あまり使われているのを見たことがないのが正直なところ。 しかーし、Java SE 7 では java.nio.file パッケージが追加されて、ファイルやパスの操作ができるようになったのでかなり手軽に扱えるようになったのではないかと思います。 また、java.io パッケージにあるクラスと競合するワケではなく、必要になればそれらのパッケージ内のクラスとも連携がとれるようなので、新しいクラス群を使うハードルも比較的低くなったと思います。

Java SE 7 では java.nio.file とそのサブパッケージが追加されました:

java.nio.file パッケージはファイル・システムやファイル操作に関連するクラス群が定義されています。 ただし、簡単なファイル操作で使用するのは

  • Path インターフェース
  • Paths クラス
  • Files クラス

だけなので恐るるにたらずです。 java.nio.file.attribute パッケージにはファイルの属性(アトリビュート)に関するクラス群が定義されています・・・って説明になってませんが。 まぁ、そのうちに。 java.nio.file.spi パッケージは自分でファイルシステムを作らない限り使わないもののようです。

今回は Path インターフェースとPaths クラスを見ていきます。

Path インターフェースと Paths クラスの宣言

java.nio.file パッケージで主要なはたらきをするのは、Path インターフェースとそのインスタンスを生成する Paths クラスです*2。 まずはそれぞれの型宣言を見ていきましょう。

Path インターフェース
Path インターフェースは、その名の通りファイルやディレクトリのパスを表す型です。 役割としては java.io.File と同じですが、もっと適切な名前が付けられました。 Path オブジェクトは、java.io.File と同じく、そのオブジェクトが表現するファイルやディレクトリが存在しなくてもインスタンスとして存在することができます。 型宣言はこんなの:

package java.nio.file;

public interface Path extends Comparable<Path>, Iterable<Path>, Watchable{

    // Object, Comparable, Iterable のメソッドは省略

    //*****パスの情報を取得する *****
    public int getNameCount()
    public Path getName(int index)
    public Path getFileName()
    public Path subpath(int beginIndex, int endIndex)

    public boolean isAbsolute()
    public boolean startsWith(Path other)
    public boolean startsWith(String other)
    public boolean endsWith(Path other)
    public boolean endsWith(String other)

    //***** 別のファイル、ディレクトリへのパスを生成する *****
    public Path getParent()
    public Path getRoot()
    public Path resolveSibling(Path other)
    public Path resolveSibling(String other)
    public Path resolve(Path other)
    public Path resolve(String other)

    //***** 等価なパスの別表現を生成する *****
    public Path normalize()
    public Path toAbsolutePath()
    public Path toRealPath(LinkOption... options) throws IOException
    public Path relativize(Path other)

    //***** 他のオブジェクトへ変換する *****
    public File toFile()
    public URI toUri()
    public String toString()

    //***** その他のメソッド *****
    public FileSystem getFileSystem()

    public WatchKey
    register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException

    public WatchKey
    register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)
        throws IOException
}

Comparable は java.io.File も拡張してましたが、Path は Iterable<Path> なども継承してます。 ただし、このイテレータはパスの要素を列挙するもので、ディレクトリ内のファイルを列挙するようなものではありません。 Watchable に関しては気が向けばそのうちに。

定義されているメソッドに関しては、ファイル操作(ファイルの生成や削除など)に関連するメソッドが java.nio.file.Files クラスに移され、定義されているメソッドはパス操作に関連するもののみとなっています。 独断と偏見でメソッドを分類するとこんな感じ:

それぞれ、後で見ていきます。

いろいろなアプリケーションでファイルの内容を読み込むパーサ・クラスが使われていると思いますが、それらのクラスに Path オブジェクトから読み込むメソッド(read(Path) や parse(Path) のようなもの)が定義されていけば java.nio.file パッケージも少しは広まっていくんじゃないでしょうかね。

Paths クラス
Paths クラスは、Path オブジェクトを生成するためのユーティリティ・クラスです(ファクトリ・クラスといっていいのかな?)。 定義されているのは Path オブジェクトを生成する static メソッド get() だけです:

package java.nio.file;

public final Class Paths{

    public static Path get(String first, String... more);
    public static Path get(URI uri);
}

使い方は「Path オブジェクトを生成する」の箇所参照。

Path オブジェクトを生成する

Path オブジェクトを取得するためには Paths クラスに定義されている static メソッド get() を用います。 このメソッドは以下の2つがオーバーロードされています:

メソッド 返り値 説明
get(String...)
get(java.net.URI)
Path Path オブジェクトを生成する。

使い方を示すサンプルコードを見てみましょう(コードは Groovy で書いてます):

import java.nio.file.*

// Path オブジェクトの生成は Paths#get() メソッドで
def path = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")

// バックスラッシュでも区切れる
assert path == Paths.get("C:\\sample\\code\\of\\java\\nio\\file\\PathUsage.java")
    // Groovy での「==」は等値評価(equals() による評価)

// 複数の文字列から生成
assert path == Paths.get("C:", "sample", "code", "of", "java", "nio", "file", "FileUsage.java")

// java.net.URI から生成
def uri = URI.create("file:///C:/sample/code/of/java/nio/file/PathUsage.java")
assert path == Paths.get(uri)

// compareTo() メソッド
def path1 = Paths.get("C:/sample/code/of/java/nio/file/FileUsage.java")
assert path > path1    // assert path.compareTo(path1) > 0

このサンプルは Windows 上で動かすことを念頭において書いてますが、Windows 上でもパス要素の区切りはスラッシュ (/) で動きます*3。 もちろんバックスラッシュ (\) でも OK ですがエスケープが面倒なのでここではあまり使いません*4

パスの情報を取得する

次はその Path オブジェクトからパス情報を取得するメソッドを見ていきます。 これらの取得できる情報は、パス要素を分離して List になっていると思えば分かりやすいでしょう。 例えば

    C:/abc/def/ghi

というパスがあれば、

    ["abc", "def", "ghi"]

という List だと思えば各メソッドの振る舞いが分かりやすいと思います。 ただし、ここでは要素が String のように書きましたが、実際はそれぞれすべてが Path オブジェクトです。 また、絶対パスに関しては startsWith() はルートから始める必要があります。

メソッド 返り値 説明
getNameCount() int パス要素の個数を返す。
getName(int) Path 引数で指定された位置のパス要素を返す。
getFileName() Path 最後のパス要素を返す*5
subpath(int, int) Path 引数で指定された部分パスを返す。
isAbsolute() boolean 絶対パスかどうかを返す
startsWith(String)
startsWith(Path)
Path 引数で指定されたパスから始まるかどうかを返す。
endsWith(String)
startsWith(Path)
Path 引数で指定されたパスで終わるかどうかを返す。

では Groovy のサンプルコード:

def path = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")

println path.getNameCount()    // 「7」と表示

// 部分パスを返すメソッド。 返り値は Path オブジェクト
println path.getName(0)    // 「sample」と表示
println path.getFileName()    // 「PathUsage.java」と表示
println path.subpath(0, 3)    // 「sample\code\of」と表示

// boolean 値を返すメソッド
assert path.isAbsolute()

assert path.startsWith("C:/")    // String で startsWith()
assert !path.startsWith("sample")    // 絶対パスの場合はルートから始まる

assert path.endsWith(Paths.get("PathUsage.java"))    // Path で endsWith()
assert !path.endsWith("Usage.java")    // 文字列で比較してるわけではない

等価なパスの別表現を生成する

ここでは既に存在している Path オブジェクトを、それと等価な(同じパスを表す)別の Path オブジェクトへ変換するメソッドを見ていきます。 具体的に言うと

です。

メソッド 返り値 説明
normalize() Path パスを正規化する(., .. などを使わない表現にする)。
toAbsolutePath() Path 絶対パスに変換する。
toRealPath() Path パスが表すファイル・ディレクトリが存在すれば絶対パスに変換し、
なければ java.nio.file.NoSuchFileException を投げる。
relativize(Path) Path このオブジェクトが表すパスに対する引数のパスの相対パスを返す。

では Groovy によるサンプルコード:

def path = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")

// 余剰なパス表現(., .. など)を正規化
def redundantPath = Paths.get("C://sample/code/of/./java/nio/file/../file/PathUsage.java")
println redundantPath.normalize()
    // 「C:\sample\code\of\java\nio\file\PathUsage.java」と表示

// 絶対パスに変換
println path.toAbsolutePath()    // 「C:\sample\code\of\java\nio\file\PathUsage.java」と表示
println path.toRealPath()
    // ファイルが実際に存在すれば「C:\sample\code\of\java\nio\file\PathUsage.java」と表示
    // 存在しなければ java.nio.file.NoSuchFileException が投げられる

// 相対パスに変換
def nio = Paths.get("C:/sample/code/of/java/nio")
println nio.relativize(path)    // 「file\PathUsage.java」と表示
    // 引数のパスと等価なパスが返される

toRealPath() メソッドは唯一実際に対応するパスがないと例外を投げるメソッドです。

別のファイル、ディレクトリへのパスを生成する

ここでは Path オブジェクトに関係する別の Path を生成するメソッドを見ていきます。

メソッド 返り値 説明
getParent() Path ディレクトリを返す。
getRoot() Path ルート・ディレクトリを返す。
resolveSibling(String)
resolveSibling(Path)
Path 兄弟要素(同ディレクトリ内のファイル、ディレクトリ)を返す。
resolve(String)
resolve(Path)
Path このオブジェクトが表すパスを起点にして、
引数のパスが相対的に表すパスを返す*6

では Groovy のための(?)サンプルコード:

def path = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")

// 親ディレクトリ
println path.getParent()    // 「C:\sample\code\of\java\nio\file」と表示

// ルート・ディレクトリ
println path.getRoot()    // 「C:\」と表示

// 兄弟要素(同ディレクトリ内のファイル、ディレクトリ)
println path.resolveSibling("FilesUsage.java")
    // 「C:\sample\code\of\java\nio\file\FilesUsage.java」と表示

resolve() メソッドは別途サンプルを書きます:

Path dir1 = Paths.get("C:/sample/code/of/java/nio/file")
println dir1.resolve("FilesUsage.java")
    // 「C:\sample\code\of\java\nio\file\FilesUsage.java」と表示

// 末尾のスラッシュ (/) は無影響
def dir2 = Paths.get("C:/sample/code/of/java/nio/file/")
println dir2.resolve("FilesUsage.java")
    // 「C:\sample\code\of\java\nio\file\FilesUsage.java」と表示

// 以下のようにしても例外は出ない(PathUsage.java がファイルかディレクトリか分からないからだろうけど)
def file = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")
println file.resolve("FilesUsage.java")
    // 「C:\sample\code\of\java\nio\file\PathUsage.java\FilesUsage.java」と表示

他のオブジェクトへ変換する

最後は他のオブジェクトへ変換するメソッド。 Path オブジェクトには今までの Java I/O で使われていた java.io.File や java.net.URI に変換するメソッドが定義されています:

変換メソッド 変換される型
toFile() java.io.File
toUri() java.net.URI
toString() String

これらのメソッドによって、いざとなれば今までの Java I/O のプログラミングで使われていたクラスを取得できるので、Path クラスを使う閾も低くなると思います。 ちなみに、java.io.File クラスには対応する Path クラスへ変換する toPath() メソッドも追加されています。

メソッドの使い方は特に問題ないかと:

def path = Paths.get("C:/sample/code/of/java/nio/file/PathUsage.java")

assert path.toFile() == new File(/C:\sample\code\of\java\nio\file\PathUsage.java/)
assert path.toUri() == URI.create("file:///C:/sample/code/of/java/nio/file/PathUsage.java")
assert path.toString() == "C:\sample\code\of\java\nio\file\PathUsage.java"    // Windows

ちなみに、Windowsjava.io.File クラスのコストラクタにスラッシュ (/) で区切られたパスを渡してもキチンと動作します。

その他のメソッド

その他、今回扱わなかったメソッドには

メソッド 返り値
getFileSystem() FileSystem
register(WatchService watcher, WatchEvent.Kind<?>... events)
register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)
WatchKey

があります。 これらについては後日やるかも。 やらないかも。

現場で使えるJavaライブラリ

現場で使えるJavaライブラリ

*1:というか、触れる能力がない:-p

*2:Files クラスも重要ですが後回し。

*3:これは java.io.File も同じ。

*4:Groovy のスラッシュ (/) による文字列リテラルを使えばエスケープする必要はありませんが。

*5:返されたものがファイル名なのかディレクトリ名なのかは関係ありません。

*6:起点となるこの Path オブジェクトがファイルかディレクトリかは考慮されません。