JavaのString.replaceAllとは?スマートに文字列置換するためのreplaceAllの使い方
String.replaceAllは、文字列中を正規表現でマッチングし、マッチした部分を置換するものです。
同じような動きをするものとして、replaceFirstがあります。
この記事では、replaceAll/replaceFirstの使い方と、プログラミングの現場でよく見かける応用例を初心者向けにお伝えします。
なお、似たメソッドとしてString.replaceがあります。こちらは正規表現ではなく固定された文字列を対象に置換します。
こちらも初心者向けの記事がありますので、よろしければご覧ください。
※この記事のサンプルは、Java 10の環境で動作確認しています。
目次
1.String.replaceAllは正規表現のマッチ箇所を置換する
String.replaceAllは、「正規表現」がマッチした箇所全てを、指定の文字列に置換した文字列を戻すメソッドです。それ以外の機能はString.replaceと同じです。
public String replaceAll(String regex, String replacement) regex - この文字列との一致を判定する正規表現 replacement - 一致するものそれぞれに置き換えられる文字列
正規表現(Regular Expression)とは、文字や数字の「パターン」を指定するものです。replaceAllが真価を発揮するのは、置換したい文字列に何かしらの明確なパターンがある時です。
1-1.例:英語小文字・数字とのマッチング
例えば、正規表現の[a-z]は英語の小文字(aからzの全て)にマッチしますし、[0-9]は数字(0~9の全て)にマッチします。実際には以下のようにして用います。
String str = "The quick brown fox jumps over the lazy dog. 1234567890"; // aからzの範囲にある文字なら何でもxに置換する String replaced1 = str.replaceAll("[a-z]", "x"); System.out.println(replaced1); // → Txx xxxxx xxxxx xxx xxxxx xxxx xxx xxxx xxx. 1234567890 // 0から9の範囲にある文字なら何でもyに置換する String replaced2 = str.replaceAll("[0-9]", "y"); System.out.println(replaced2); // → The quick brown fox jumps over the lazy dog. yyyyyyyyyy // aからzあるいは0から9にある文字なら何でもzに置換する String replaced3 = str.replaceAll("[a-z0-9]", "z"); System.out.println(replaced3); // → Tzz zzzzz zzzzz zzz zzzzz zzzz zzz zzzz zzz. zzzzzzzzzz
ちなみに、同じようなことをString.replaceで行うには、例えば以下のようにしなければなりません。行数は増えますし、メソッドを呼び出す回数が多いので性能も悪そうです。そう見ると、正規表現は効率が良いですね。
String str = "The quick brown fox jumps over the lazy dog"; String replaced = str.replace("a", "x"); replaced = replaced.replace("b", "x"); : replaced = replaced.replace("y", "x"); replaced = replaced.replace("z", "x"); // aからzまで延々と続けるか、a-zでループする
もちろん、正規表現へは日本語も使えます。(いわゆる)全角半角もJavaの内部ではきちんと別物なので注意しましょう。
// 日本語でも大丈夫! String str1 = "私は犬が大好きです。犬は人類の友です。"; String str2 = "鳥類とは、鳥綱すなわち脊椎動物亜門の一綱に属する動物群の総称。"; String regex = "[犬鳥]"; // 犬あるいは猫にマッチする System.out.println(str1.replaceAll(regex, "猫")); // → 私は猫が大好きです。猫は人類の友です。 System.out.println(str2.replaceAll(regex, "猫")); // → 猫類とは、猫綱すなわち脊椎動物亜門の一綱に属する動物群の総称。
1-2.Javaで使える正規表現のパターン例
正規表現では以下のものを覚えておけば最初の内は大体OKです。もっと凝ったことをやりたければ、そこからどんどん応用していけばいいのです。
[] ← []の中に指定した文字との一致
[a-z] ← 英語小文字
[A-Z] ← 英語大文字
[0-9] ← 数字
() ← 文字列のグループ化
| ← グループ化した文字列内での選択
+ ← 文字あるいは文字列が1回以上繰り返し出現する
* ← 文字あるいは文字列が0回以上繰り返し出現する
^ ← 文字列の先頭
$ ← 文字列の末尾
なお、Javaで使える正規表現のパターンの詳細は、例えばJavadocのjava.util.regex.Patternをご覧ください。記載されている分量は多いですが、実務で頻繁に使うのはその中の一部です。
1-3.特殊文字の無効化には\かPattern.quoteを使う
正規表現では、前述のとおり[]、()、\、|などいくつかの文字を特別な意味に用います。これらの文字をそのまま正規表現中で指定したい場合は、\により無効化しなければなりません(一般にはエスケープと呼ばれます)。
以下の例では、正規表現では文字を選択指定する [] 自体を置換したいのですが、そのままでは正規表現の文法エラーになってしまいます。
String str = "Javaでは配列の要素は[]の中に添え字を書いてアクセスします。"; String replaced = str.replaceAll("[]", "角括弧"); // → java.util.regex.PatternSyntaxException: Unclosed character class near index 1 System.out.println(replaced);
これを動くようにするには、以下のようにして特別な意味を持つ文字をエスケープします。\\を二つ書くのは、最終的に\[\]という文字列にしたいからです。Javaの “” の中では、\自体が特別な意味を持つからですね。
String str = "Javaでは配列の要素は[]の中に添え字を書いてアクセスします。"; String replaced = str.replaceAll("\\[\\]", "角括弧"); // → OK!! System.out.println(replaced); // → Javaでは配列の要素は角括弧の中に添え字を書いてアクセスします。
ただし、いちいち手でエスケープするのも大変なので、まとめて全部エスケープしたいならPattern.quoteが使えます。実行結果に付く\Qと\Eは、範囲内の全ての特殊文字をエスケープするものです。正確に行うには色々考慮する必要がありますが、それを全部やってくれます。
/ Patternでのメソッド定義 public static String quote(String s) s - リテラル化する文字列
String str = "Javaでは配列の要素は[]の中に添え字を書いてアクセスします。"; String regex = Pattern.quote("[]"); String replaced = str.replaceAll(regex, "角括弧"); // → OK!! System.out.println(replaced); // → Javaでは配列の要素は角括弧の中に添え字を書いてアクセスします。 System.out.println(regex); // → \Q[]\E
2.String.replaceFirstは最初のマッチ箇所だけを置換する
String.replaceFirstは、replaceAllの限定版で、正規表現にマッチした最初の一箇所目だけを置換します。
public String replaceFirst(String regex, String replacement) regex - この文字列との一致を判定する正規表現 replacement - 最初に一致するものに置き換えられる文字列
ですので、以下の例では最初に出現した英語小文字(Theのh)のみが置換されました。
String str = "The quick brown fox jumps over the lazy dog. 1234567890"; String replaced = str.replaceFirst("[a-z]", "x"); System.out.println(replaced); // → Txe quick brown fox jumps over the lazy dog. 1234567890
3.【応用】String.replaceAllの応用例
String.replaceAll/replaceFirstの強みは、置換対象の文字列を正規表現で指定できることです。この指定をどう工夫するかで、色々な応用ができるのです。以下はその一例です。
3-1.空白の削除や詰め
以下のように空白削除にも使えます。簡単かつ地味ですが、利用頻度は結構高いですよ。置換先の文字列を空文字列(長さ0の文字列、“”)とすれば、削除と同じ意味になるのです。
String str = "A B C D"; String replaced = str.replaceAll(" ", ""); System.out.println(replaced); // → ABCD
空白扱いしたい文字は正規表現で自由に指定できますので、例えば以下のようにもできます。
String str = "A B\tC\nD"; // タブ(\t)と改行(\n)を含む String replaced = str.replaceAll("[ \t\n] ", ""); System.out.println(replaced); // → ABCD
パターンを少し変えるだけで、複数の空白を1つにまとめるのも簡単にできます。いくつ空白があってもパターンにマッチすればいい、ということが大きな強みです。
String str = "A B C D"; String replaced = str.replaceAll(" +", " "); System.out.println(replaced); // → "A B C D"
3-2.全角・半角空白両対応trim
標準のString.trimは空白(※)だけ削除してくれますが、私たち日本人だと(いわゆる)全角空白もtrimしたいですよね。その場合も、replaceFirstを使えば楽々です。※正確には空白よりコードポイントが小さい文字
String str = " A B C D "; // ←先頭・末尾に全角半角空白が混在しています String replaced = str.replaceFirst("^[\\s ]+", "").replaceFirst("[\\s ]+$", ""); System.out.println(replaced); // → "A B C D"
要は、先頭(^)の後と末尾($)の前で空白扱いしたいものがあれば、それを“”に置換しているだけです。replaceAllでも結果は同じですが、1回だけになるのでこちらの方が意味が明確になるでしょう。
trimする範囲を先頭だけ、末尾だけとしたいなら、どちらか片方のreplaceFirstだけを実行すればOKです。
3-3.特定文言のマスク・文言の修正
何かの文章の中に含まれる特定キーワードをマスクしたい場合があります。replaceでも良いですが、数が多い場合だと一つのパターンで全部賄ってしまった方が楽な場合もあります。キーワードを|で繋いでいくだけです。
String str = "The quick brown fox jumps over the lazy dog"; String replaced = str.replaceAll("fox|dog", "xxx"); System.out.println(replaced); // → The quick brown xxx jumps over the lazy xxx
ちなみに、大文字・小文字を無視したいなら以下のようにもできます。(?i)は大文字・小文字の区別をなくすためのオプションです。この他にも全角半角を同一視するものなど色々なオプションがありますので、興味があれば「java 正規表現 オプション」などのキーワードで調べてみてもいいでしょう。
String str = "The quick brown FoX jumps over the lazy dOg"; String replaced = str.replaceAll("(?i)fox|dog", "xxx"); System.out.println(replaced); // → The quick brown xxx jumps over the lazy xxx
これはちょっとした文言の修正にも使えます。この例は音引きの修正で、文中の「コンピュータ」あるいは「コンピューター」をどちらかに統一するものです。ここから工夫をすれば、送り仮名の統一にも使えますね。
String str = "コンピューターとコンピュータは同じもの?"; String replaced1 = str.replaceAll("(コンピュータ)([ー])", "$1"); System.out.println(replaced1); // → コンピュータとコンピュータは同じもの? String replaced2 = str.replaceAll("(コンピュータ)([^ー])", "$1ー$2"); System.out.println(replaced2); // → コンピューターとコンピューターは同じもの?
3-4.改行コードや区切り文字の統一
1つの文字列やファイルの中に改行コードが混在していることがあります。Windows系なら\r\n、UNIX系なら\n、古いMacなら\rです。それを統一するのも簡単です。正規表現の中では、\を指定したい場合は\\を二つ重ねる必要があることに注意しましょう。
String str = "A\r\nB\rC\nD"; String replaced = str.replaceAll("\\r\\n|\\r|\\n", "\r\n"); System.out.println(replaced); // → "A\r\nB\r\nC\r\nD" System.out.println(Arrays.toString(replaced.getBytes())); // → [65, 13, 10, 66, 13, 10, 67, 13, 10, 68]
複数ある区切り文字を一つにするのも同じように一発です。ログなどを分析しやすくするため、タブなどにひとまとめにするのに使えますね。
String str = "A,B\tC-D"; String replaced = str.replaceAll("[,\t\\-]", ","); System.out.println(replaced); // → A,B,C,D
3-5.HTML/XML中の属性値の削除
HTML中で特定の条件を満たす属性値(attribute)をごっそり削除できたりします。削除したい条件が、正規表現で上手く表現できるかがポイントです。ここでは、属性名がheightかつ属性値が数値のものを選んでいます。
String str = "<img height=\"100\" class=\"main\" /><img height=\"50\" class=\"footer\" />"; String replaced = str.replaceAll(" height=\"\\d+\"", ""); System.out.println(replaced); // → <img class="main" /><img class="footer" />
4.【中級者向け】String.replaceAllのもう少し進んだ話題
4-1.マッチした文字列の参照($0、$1、$2…)
String.replaceAllの2番目の引数の中では、正規表現中で「()」で囲んだ部分を「$ + 数値」という形式で参照できます。例えば、以下のようにします。
String str = "1000,5000,10000"; String replaced = str.replaceAll("([0-9]+)", "$0円"); System.out.println(replaced); // → 1000円,5000円,10000円
ここで、$0はマッチした部分全体で、上記の例ではそれぞれの数字部分です。これは順番に$1、$2、$3…と続けられ、正規表現内で()により囲まれた部分を順番に参照できます。以下では、マッチした部分の並び替えをしています。
String str = "ABC1,D2EF,3GHI"; String replaced = str.replaceAll("([A-Z0-9]+),([A-Z0-9]+),([A-Z0-9]+)", "$2,$3,$1"); System.out.println(replaced); // → D2EF,3GHI,ABC1
これの使いどころは少し難しいです。ですが、先述した応用例でも一部この機能を使っているところがありますので、使いようによっては便利なものなのです。
4-2.PatternとMatcherで正規表現の世界に飛び込もう!
String.replaceAllは、実は正規表現のAPIであるjava.util.regex.Patternとjava.util.regex.Matcherを意識せずに使うためのものです。裏では以下のような処理が行われています。
String str = "The quick brown fox jumps over the lazy dog. 1234567890"; Pattern pattern = Pattern.compile("[a-z]"); Matcher matcher = pattern.matcher(str); String replaced = matcher.replaceAll("x"); System.out.println(replaced); // → Txx xxxxx xxxxx xxx xxxxx xxxx xxx xxxx xxx. 1234567890
正規表現で凝ったことをやりたい場合は、String.replaceAllだけでは限界がありますので、Matcherの力を借りる必要があります。
一例として、マッチした部分をすべて出力するには以下のようにします。String.replaceAllで$0や$1、$2で参照できていたものです。
String str = "1000,5000,10000"; Pattern pattern = Pattern.compile("([0-9]+)"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { System.out.println(matcher.group(0)); // → 1000、5000、10000が順に表示される }
String str = "ABC1,D2EF,3GHI"; Pattern pattern = Pattern.compile("([A-Z0-9]+),([A-Z0-9]+),([A-Z0-9]+)"); Matcher matcher = pattern.matcher(str); if (matcher.find()) { for (int i = 1; i <= matcher.groupCount(); i++) { System.out.println(matcher.group(i)); // → ABC1、D2EF、3GHIが順に表示される } }
これはMatcherのほんの一機能です。もっといろいろな凄いことができますので、ぜひやってみましょう。作業効率化に繋がるものがあるかもしれません。
5.まとめ
String.replaceAllは、正規表現のパターンにマッチする部分を指定した文字列で置換するものです。replaceFirstは、最初にマッチした部分だけ置換するものです。
この記事では応用例をいくつかお伝えしましたが、それでも正規表現が持つ力をほんの少し垣間見ただけです。String.replaceAllは、正規表現を簡易的に扱うためのものでしかありません。Javaで正規表現に真の力を発揮させるためには、java.util.Patternとjava.util.Matcherを深く知る必要があります。
String.replaceAllを足掛かりに、ぜひ正規表現の世界に足を踏み入れましょう。Javaに限らず、エディタなどでも正規表現を使えることがほとんどですので、上手く使いこなせればあなたの作業効率が一気に良くなりますよ!!
コメント