Pathクラス

その名前が示すように、Pathクラスは、ファイルシステム内のパスをプログラムで表現したものです。
Pathオブジェクトには、パスを構築するために使用されるファイル名とディレクトリリストが含まれ、ファイルの検査、検索、および操作に使用されます。

Pathインスタンスは、基礎となるプラットフォームを反映します。
Solaris OSでは、パスはSolaris構文(/ home / joe / foo)を使用し、
Microsoft WindowsではパスはWindows構文(C:¥home¥joe¥foo)を使用します。
パスはシステムに依存しません。ディレクトリ構造が同一で、両方のインスタンスが同じ相対ファイルを見つけても、
Solarisファイルシステムのパスを比較して、Windowsファイルシステムのパスと一致するとは限りません。

パスに対応するファイルまたはディレクトリが存在しない可能性があります。
Pathインスタンスを作成し、さまざまな方法で操作することができます。追加することができ、
そのインスタンスを抽出し、別のパスと比較することができます。
適切なタイミングで、Filesクラスのメソッドを使用して、Pathに対応するファイルの存在を確認し、
ファイルを作成し、開いて、削除したり、権限を変更したりすることができます。

Java8チュートリアル「The Path Class」より引用

Pathクラスには、パスに関する情報の取得、パスの要素へのアクセス、パスの他のフォームへの変換、
またはパスの一部の抽出に使用できるさまざまなメソッドが含まれています。パス文字列を一致させる方法と、
パス内の冗長性を除去する方法もあります。これらのPathメソッドについて説明します。
これらのパスメソッドは、パス上で動作し、ファイルシステムにアクセスしないため、構文操作と呼ばれることもあります。

Java8チュートリアル「Path Operations」より引用

Pathのインスタンス生成

筆者が最初に「so What」と思った事が、PathはAPIで見るとクラスではなく、インターフェイスです。
インターフェイスは、実装クラスの公開メソッドを定義しているだけです。

それでは、最初のサンプルを動かしてインスタンスを確認してみましょう。

Pathのインスタンス生成
    Path path = Paths.get("C:/projects/education");
    System.out.println( path.getClass().getName() );
					
実行結果
sun.nio.fs.WindowsPath
					

Windows10のOSでは、「sun.nio.fs.WindowsPath」が出力されました。 Linux系、Unix系のOSでは他のPathクラスが出力されます。
このようにPathインターフェイスの実体は、Pathsクラスからパスを文字列を引き渡して生成します。

Pathのインスタンス生成
Path 変数名 = Paths.get( パス名 );

Pathの階層情報

Pathクラスのインデックス

Pathには階層情報を階層の深さをインデックスで示して指定した階層のディレクトリ名などの情報を取得できます。
上記図の例のように最上位のルートパス(Windowsの場合ドライブ名,Linux,Unix系の場合「/」)から1階層目の「education」は、
インデックスでは0になり、最下位層の「main」は「ディレクトリ階層数-1」即ち2になります。

Pathの各種メソッドのサンプル

新しく追加されたAPIを理解するには、リリース時のTutorialsのドキュメントを読み、
実際に簡単なサンプルを作成して確認するのが良いでしょう。

Pathのメソッド
    /**
     * パスの各種メソッドを利用するサンプル
     */
    private void pathSamples(){

        Path path = Paths.get("C:/projects/education/src");

        System.out.println("toString:" + path.toString());
        System.out.println("getName(0).toString:" + path.getName(0).toString());
        System.out.println("getName(1).toString:" + path.getName(1).toString());
        System.out.println("getName(2).toString:" + path.getName(2).toString());
        System.out.println("getNameCount:" + path.getNameCount());
        System.out.println("getRoot().toString:" + path.getRoot().toString());
        System.out.println("getParent().toString:" + path.getParent().toString());
        System.out.println("getFileName()" + path.getFileName());
        //このメソッド要注意
        System.out.println("subpath(1,3).toString:" + path.subpath( 1 , 3 ).toString() );

        System.out.println("toUri().toString:" + path.toUri().toString());
        System.out.println("resolve(\"main\").toString:" + path.resolve("main").toString());

    }
					
実行結果
toString:C:¥projects¥education¥src
getName(0).toString:projects
getName(1).toString:education
getName(2).toString:src
getNameCount:3
getRoot().toString:C:¥
getParent().toString:C:¥projects¥education
getFileName()src
subpath(1,3).toString:education¥src
toUri().toString:file:///C:/projects/education/src/
resolve("main").toString:C:¥projects¥education¥src¥main
					

JavaAPIとサンプルの実行結果でほとんど何の説明も無しに理解できる内容ばかりです。
ただし、subpath( startIndex , endIndex )は要注意です。
開始位置を示すstartIndexは、上記例では0から2の値が指定できます。
指定すると開始位置のパスは結果に含まれます。
逆に終了位置を示すendIndexは、0から2ではなく1から3を指定できます。
APIの仕様でendIndexを指定したパスを含まないため階層情報が1つ増えているいます。

また、ディレクトリ階層を連結するresolveにも注意が必要です。
「C:/projects/education/src」にmainの階層を追加するだけですが、
path.resolve("main")とmainの単語の前に「/」ディレクトリ区切り文字のスラッシュを指定していません。
これは、「C:/projects/education/src」からの相対パスからのmainを意味しています。
「/main」を指定すると「C:/main」になるので注意が必要です。

2つのパスの比較メソッド
    /**
     * 2つのパスの比較
     */
    private void pathCompare(){

        Path basePath = Paths.get("C:/projects/education/src");
        Path basePath2 = Paths.get("C:/projects/education/src");

        Path path = Paths.get("src");
        if( basePath == basePath2 ){
            System.out.println("basePath instance is equal basePath2 instance" );
        }else{
            System.out.println("basePath instance is not equal basePath2 instance" );
        }

        if( basePath.equals( basePath2 )){
            System.out.println("basePath is equal basePath2" );
        }else{
            System.out.println("basePath is not equal basePath2" );
        }

        if( basePath.startsWith( path )){
            System.out.println("basePath is startsWith src" );
        }else{
            System.out.println("basePath is not startsWith src" );
        }
        if( basePath.startsWith( "C:\\projects" )){
            System.out.println("basePath is startsWith /projects" );
        }else{
            System.out.println("basePath is not startsWith /projects" );
        }

        if( basePath.endsWith( path )){
            System.out.println("basePath is endsWith src" );
        }else{
            System.out.println("basePath is not eWith src" );
        }
    }
					
実行結果
basePath instance is not equal basePath2 instance
basePath is equal basePath2
basePath is not startsWith src
basePath is startsWith /projects
basePath is endsWith src
					

この結果から見て、Pathのインスタンス生成時に指定したパスの文字列が等しくても、
生成されたインスタンスは、それぞれ別のインスタンスを生成されています。

パスが等しいかを判断するには、Stringで文字列の一致を確認するのと同じようにequalsメソッドを利用します。

ファイルの監視サービス

PathのAPIを確認するとWatchKey register(WatchService watcher,WatchEvent.Kind<?>... events) throws IOExceptionがあります。
このメソッドのコメントをみると「このパスで検出されたファイルを監視サービスに登録します。」とあります。

これは何を意味するかと言えば、指定したディレクトリの変化(ファイルの変更、追加、削除、ディレクトリの追加、変更、削除)を
監視するアプリケーションを作成できることを意味します。
これは、素晴らしい機能です。バッチシステムやシステム間連携などでリアルタイムにディレクトリやファイルの変化を監視するには、
JVMの常駐クラス(while文による無限ループ)で、定周期(何秒経過したら、処理を実行する)でFileオブジェクトを生成して毎度アクセスする必要がありました。
そのようなアプリのメンテは、上級者プログラマが居ないと直ぐにバグが起きて多くの問題を起こす監視アプリでした。

それが、java.nioパッケージの監視サービスを利用することで、複雑なコーディング無しにファイルの監視をできるようになります。