Featured image of post Java8でURLのパスを分解して文字列を取得する方法【Paths】

Java8でURLのパスを分解して文字列を取得する方法【Paths】

Java8でURLのパス文字列を分解して末尾や途中の値を取り出す方法。正規表現を使わず、java.nio.fileのPathsクラスとgetFileName・getParent・getNameで扱えます。末尾がスラッシュの場合の挙動も♪

2107文字

URLに含まれるパスの文字列を取得したい

先日、URL文字列に含まれている文字列を取得したい、という要件が出てきました。

例えば、/2018/12/02/7077という文字列があった場合に、post_idを表す7077という値を取得したい、と言うケースです。

正規表現等を利用すれば力技で取得する事は可能ですが、黒魔術になりがちなので筆者としては避けたかった為、いろいろ調べてみたところ、Javaのjava.nio.fileパッケージに含まれるPathsクラスを利用することで簡単に取得する事が出来ました。

手順

対象文字列をPathクラスにラップ

まずはこのURL文字列を扱いやすくするために、Pathsクラスのstaticメソッドであるgetメソッドを利用してPathクラスを生成しましょう。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077"; // 対象URL

    Path path = Paths.get(TARGET); // Pathクラスを生成
  }
}

getFileNameメソッドで末尾のパス文字列を取得

次にPathクラスのgetFileNameメソッドを利用して、末尾の文字列情報を取得します。
ちなみに、このメソッドの戻り値はPathクラスとなっているので、しっかりとtoString()メソッドを利用して文字列化する必要があります。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    String result = path.getFileName().toString(); // 文字列取得
  }
}

確認

では、result変数を標準出力して動作確認をしてみましょう。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    String result = path.getFileName().toString();

    System.out.println(result);  // 標準出力
  }
}

javac PathsSample.java && java PathsSample
7077

期待通りの値が取得できましたね♪

Paths.getはFileSystems.getDefault().getPathと同じ

今回利用したPaths.getメソッドはFileSystems.getDefault().getPathの省略表現となっています。
なので、FileSystems.getDefault().getPathを利用しても同じ動きは取れます。

PathsSample.java
import java.nio.file.Path;
//import java.nio.file.Paths;
import java.nio.file.FileSystems;


public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET_WITH_SLASH = "/2018/12/02/7077/";

    //Path path = Paths.get(TARGET_WITH_SLASH);
    Path path = FileSystems.getDefault().getPath(TARGET_WITH_SLASH);
    String result = path.getFileName().toString();

    System.out.println(result);
  }
}

javac PathsSample.java && java PathsSample
7077

ですが、直感的にもわかりやすいので、Paths.getメソッドを使うことを推奨します。

スラッシュ(/)終わりの場合はどうなる?

URLによってはスラッシュ(/)終わりの場合もありえますよね。
その場合も正しく動くか確認してみましょう。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET_WITH_SLASH = "/2018/12/02/7077/";

    Path path = Paths.get(TARGET_WITH_SLASH);
	  String result = path.getFileName().toString();

    System.out.println(result);
  }
}

javac PathsSample.java && java PathsSample
7077

スラッシュ終わりでも、問題なく同様の値が取得できましたね♪
気にする必要は無さそうです。

他のメソッドも試してみよう。

Pathクラスには他にも様々なメソッドがあったのできになるものを試してみました。

getRoot

まずはgetRootメソッドです。
以下のようなソースで実行してみましょう。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    String getRootResult = path.getRoot().toString(); URLに対してgetRootを行うとNULLが返る
    System.out.println(String.format("getRoot: [ %s ]", getRootResult));

  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /2018/12/02/7077 ]
=================================
Exception in thread "main" java.lang.NullPointerException
	at PathsSample.main(PathsSample.java:15)

まさかのぬるぽエラーです・・・!

どうやら、getRootメソッドは対象のパスがルート・コンポーネントを持たない場合nullを返します。
今回はURLパスのためルートの概念が存在しないためぬるぽが発生したと思われます。

以下のようなディレクトリパスの場合は正しく処理が実行されました。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;

public class PathsSample{
  public static void main(String... args)
  {

    final String TARGET = "/Users/blogenist/hoge/fuga";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    String getRootResult = path.getRoot().toString(); //URLに対してgetRootを行うとNULLが返る
    System.out.println(String.format("getRoot: [ %s ]", getRootResult));



  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /Users/blogenist/hoge/fuga ]
=================================
getRoot: [ / ]

URLパスの場合に、getRootメソッドでドメインが取得出来るわけではないので、気をつけたほうが良いですね。!
ドメインを取得したい」場合は後述のgetNameメソッドを利用することで取得が可能です。

getParent

次はgetParentメソッドです。

getParentメソッドは現在パスの親のパスを返します。
親を持たないパスの場合NULLが返ってきます。。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    while(path.getParent() != null)
    {
      String getParentResult = path.getParent().toString();
      System.out.println(String.format("getParent: [ %s ]", getParentResult));
      path = path.getParent();
    }
  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /2018/12/02/7077 ]
=================================
getParent: [ https:/blogenist.jp/2018/12/02 ]
getParent: [ https:/blogenist.jp/2018/12 ]
getParent: [ https:/blogenist.jp/2018 ]
getParent: [ https:/blogenist.jp ]
getParent: [ https: ]

path.getParent() != nullnullの場合は親が存在しないので終了します。

getNameCount

次はgetNameCountメソッドです。

getNameCountメソッドは対象のパスに含まれる名前要素の個数を返してくれます。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    int getNameCountResult = path.getNameCount();
    System.out.println(String.format("getNameCount: [ %s ]", getNameCountResult));
  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /2018/12/02/7077 ]
=================================
getNameCount: [ 6 ]

スラッシュ(/)で区切られた数だけカウントしているようですね。

getName

最後はgetNameメソッドです。

getNameメソッドは引数に渡したインデックス番号の名前要素を返してくれます。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    IntStream.range(0, path.getNameCount())
      .forEach(index -> {
        String getNameResult = path.getName(index).toString();
        System.out.println(String.format("getName(%s): [ %s ]", index, getNameResult));
      });

  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /2018/12/02/7077 ]
=================================
getName(0): [ https: ]
getName(1): [ blogenist.jp ]
getName(2): [ 2018 ]
getName(3): [ 12 ]
getName(4): [ 02 ]
getName(5): [ 7077 ]

先ほど、getRootメソッドでは取得出来なかったドメイン名は、インデックス番号1で指定することで取得する事が出来そうですね。

ちなみに、存在しないインデックス番号を指定すると以下のようなjava.lang.IllegalArgumentExceptionエラーが発生します。

PathsSample.java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.IntStream;

public class PathsSample{
  public static void main(String... args)
  {
    final String TARGET = "/2018/12/02/7077";

    Path path = Paths.get(TARGET);
    System.out.println(String.format("targetUrl: [ %s ]", TARGET));

    System.out.println("=================================");

    IntStream.range(0, path.getNameCount())
      .forEach(index -> {
        String getNameResult = path.getName(index).toString();
        System.out.println(String.format("getName(%s): [ %s ]", index, getNameResult));
      });
    String getNameResult = path.getName(path.getNameCount()).toString();
    System.out.println(String.format("getName(%s): [ %s ]", path.getNameCount(), getNameResult));
  }
}

javac PathsSample.java && java PathsSample
targetUrl: [ /2018/12/02/7077 ]
=================================
getName(0): [ https: ]
getName(1): [ blogenist.jp ]
getName(2): [ 2018 ]
getName(3): [ 12 ]
getName(4): [ 02 ]
getName(5): [ 7077 ]
Exception in thread "main" java.lang.IllegalArgumentException
	at sun.nio.fs.UnixPath.getName(UnixPath.java:323)
	at sun.nio.fs.UnixPath.getName(UnixPath.java:43)
	at PathsSample.main(PathsSample.java:20)

終わりに

以上のように、Java標準Pathクラスを利用する事でURLパス操作を簡単に行う事が出来ました。

正規表現を使うより、可読性や保守性が上がるので是非Pathクラスを利用してみてください♪