話のはじまり
こんにちは、来年度までに就職をしたい甘いレモン市場と言います。
皆さんはよき正規表現ライフを送っているでしょうか?正規表現を使いこなしたいですよね。
正規表現で文字列パターンを指定する際に、「\」(バックスラッシュ記号もしくは半角¥記号。区別のため、この1文字を「」付きで記述します)という文字列が含まれているか調べたくなることがあると思います。
たとえば『スッキリわかるJava入門 第4版』 の次の練習問題のシチュエーションを考えてみましょう。
練習15-2
フォルダ名が入っている変数folderと、ファイル名が入っている変数fileがあります。fileは必ず「readme.txt」のような形式をしていますが、folderは末尾に\記号が付いている場合と付いていない場合の両方がありえます。たとえば、「c:\javadev」や「c:\user\」のどちらもfolderの値としては正しい形式です。
folderとfileを連結して、「c:\javadev\readme.txt」のような完全なファイル名を表す文字列を組み立てるメソッドを作成してください。
これは、「c:\user\」はフォルダの階層関係のなかで既に子フォルダかファイルを持つ親フォルダであり、「c:\javadev」はそうなっていない末端のフォルダであるという現実的なシチュエーションを想定しています。
そこでフォルダの文字列の末尾が「\」であるか否かをチェックできればいいわけです。
正規表現の活用
条件を言い換えると、フォルダの文字列が「任意の文字列 + \」という文字列パターンに一致するか、という条件になります。この考え方で、String
クラスのmatches
メソッドを使うとどうにかなりそうですね。(落とし穴その1)
任意の文字列を表現する文字列パターンは.*
(任意の1文字の任意回の繰り返し)でした。これに「\」を表現する文字列\\
を連結した文字列パターンでマッチングを調べれば良さそうです。
public class ConcatString {
public static String concatPath(String folder, String file) {
if (!folder.matches(".*\\")) {
folder += "\\";
}
return folder + file;
}
}
これをmainメソッドで実行しましょう。
public class Main {
public static void main(String[] args) {
String folder1 = "c:\\user\\";
String folder2 = "c:\\javadev\\";
String file = "readme.txt";
System.out.println(ConcatString.concatPath(folder1, file));
System.out.println(ConcatString.concatPath(folder2, file));
}
}
これを実行すると、例外 Exception in thread "main" java.util.regex.PatternSyntaxException: Unexpected internal error near index 3
が返ってきます。バグりました!!!! 何がいけなかったのでしょうか?(落とし穴2)
風呂敷を畳む
落とし穴2の解決
「\」は特殊文字であったため、文字列リテラルとして指定する際には\\
というエスケープシーケンスで書く必要がありました。
しかし、「\」は正規表現(文字列パターン)においても特殊記号であるため、正規表現としてもエスケープシーケンスで書く必要があります。参照
つまり、「\」を文字列パターンを指定するには2重にエスケープシーケンスで書いて\\\\
と書く必要があります。
public class ConcatString {
public static String concatPath(String folder, String file) {
if (!folder.matches(".*\\\\")) {
folder += "\\";
}
return folder + file;
}
}
concatPath
メソッドをこのように書き換えて、main
メソッドを実行すると無事にコンパイルが通り、
c:\user\readme.txt
c:\javadev\readme.txt
がコンソールに表示されます。
落とし穴1の解決
ここまででフォルダの文字列が「任意の文字列 + \」という文字列パターンに一致するか、という条件をmatchesメソッドで調べました。
しかし、String
クラスには「末尾が指定された文字列で終わるか」を調べるendWith
メソッドが存在します。そのため、わざわざ文字列パターンを考えなくとも下記で済みます。
public class ConcatString {
public static String concatPath(String folder, String file) {
if (!folder.endsWith("\\")) {
folder += "\\";
}
return folder + file;
}
}
おわり
正規表現での「\」の扱い
- 正規表現で「\」を表現するには、文字列パターンで
\\\\
と書く必要がある - 「\」は文字列リテラルと正規表現の両方で特殊文字であるため、二重のエスケープが必要である
フォルダパス連結の問題
- フォルダ名の末尾に「\」があるかどうかを確認し、必要に応じて追加する処理が必要である
-
String.matches
メソッドを使用する代わりに、String.endsWith
メソッドを使用するとより簡単に実装できる
プログラミングの注意点
- 特殊文字を扱う際は、文字列リテラルと正規表現の両方でのエスケープを考慮する
- 既存のメソッド(例:endsWith)を活用することで、より簡潔で効率的なコードを書ける
私について
- 甘いレモン市場は就活中なので興味のある雇用主の方がいたら DM してください。