Java での正規表現の使い方メモ

Java で正規表現を使う方法を色々メモ。

# String クラスの正規表現を使うメソッド
`String` には正規表現を受け取るメソッドがいくつか存在する。

- [matches(String)](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#matches-java.lang.String-)
- [replaceAll(String, String)](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#replaceAll-java.lang.String-java.lang.String-)
- [replaceFirst(String, String)](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#replaceFirst-java.lang.String-java.lang.String-)
- [split(String, int)](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#split-java.lang.String-int-)
- [split(String)](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/String.html#split-java.lang.String-)

## matches(String)
```java
package sample.regexp;

public class Main {

    public static void main(String[] args) {
        String text = "abc123";

        System.out.println(text.matches("[a-z0-9]+"));
        System.out.println(text.matches("[a-z]+"));
    }
}
```

```text:実行結果
true
false
```

- 文字列が指定した正規表現と**完全に一致**することを検証する
- 一部が一致するだけの場合は `false` になる

## replaceAll(String, String)
```java
package sample.regexp;

public class Main {

    public static void main(String[] args) {
        String text = "abc123";

        System.out.println(text.replaceAll("[a-z]", "*"));
    }
}
```

```text:実行結果
***123
```

- 第一引数に正規表現を渡し、マッチする部分を全て第二引数の文字列に置換する

### 置換文字列にマッチしたグループを使用する
```java
package sample.regexp;

public class Main {

    public static void main(String[] args) {
        String text = "<<abc123>>";

        System.out.println(text.replaceAll("([a-z]+)([0-9]+)", "$0, $1, $2"));
    }
}
```

```text:実行結果
<<abc123, abc, 123>>
```

- 置換文字列に `$n` を含めることで、マッチしたグループを置換後の文字列再利用できる
    - `n` は `0` はじまり
    - `0` はマッチした文字列全体を指している
        - `([a-z]+)([0-9]+)` にマッチした部分なので、 `abc123` が対象になる
    - `1` から先は、 `()` で囲ったグループを順番に参照できる
        - `$1` は `([a-z]+)` にマッチした `abc` が、
        - `$2` は `([0-9]+)` にマッチした `123` が対象になる
- マッチしているグループの数より大きい数を `n` に指定した場合は、 `IndexOutOfBoundsException` がスローされる
- 単純に `$` という文字列に置き換えたい場合はバックスラッシュ (`\`) でエスケープする
    - `text.replaceAll("[a-z]+", "\\$")`
    - エスケープしていない場合は、 `IllegalArgumentException` がスローされる
- グループはインデックス以外にも名前を付けて名前で参照することも可能
    - 詳細は [こちら](#%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97) を参照

## replaceFirst(String, String)
```java
package sample.regexp;

public class Main {

    public static void main(String[] args) {
        String text = "abc123";

        System.out.println(text.replaceFirst("[a-z]", "*"));
    }
}
```

```text:実行結果
*bc123
```

- 正規表現にマッチした部分文字列のうち、最初にマッチした箇所だけを置換する
- `$n` で部分文字列を参照できるのは、 `replaceAll()` と同じ


## split(String, int)
```java
package sample.regexp;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        String text = "a1b2";
        
        for (int i=-1; i<5; i++) {
            String[] elements = text.split("[0-9]", i);
            System.out.println("limit=" + i + ",\telements=" + Arrays.toString(elements));
        }
    }
}
```

```text:実行結果
limit=-1,	elements=[a, b, ]
limit=0,	elements=[a, b]
limit=1,	elements=[a1b2]
limit=2,	elements=[a, b2]
limit=3,	elements=[a, b, ]
limit=4,	elements=[a, b, ]
```

- 第一引数で指定した正規表現にマッチする場所で文字列を分割する
- 第二引数の `limit` は、戻り値の配列のサイズの上限を決める
    - `limit` に `1` 以上の値を指定した場合、マッチした部分文字列の分割は `limit - 1` 番目までになる
    - `limit==1` の場合は、 `limit - 1 => 0` になるので、分割は行われない（結果、配列のサイズは `1` になる）
    - `limit==2` の場合は、 `limit - 1 => 1` になるので、正規表現 `[0-9]` に最初にマッチした `a1b2` の `1` の部分で分割が行われ、そこで分割は終了する（結果、配列のサイズは `2` になる）
    - `limit` に `0` 以下の値を指定した場合は制限無しの扱いになり、文字列の末尾まで分割が実行される
        - ただし、分割の結果末尾に文字列が残っていない（空白になった）ときの挙動が、 `0` と負数とで異なる
        - 負数の場合は、最後の空白も配列の要素として残される
        - `0` の場合は、最後の空白は破棄される

分割の結果先頭が空白になる場合は、そのまま空白が配列の要素として設定される。

```java
package sample.regexp;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        String text = "0a1b2";
        
        String[] elements = text.split("[0-9]", 0);
        System.out.println(Arrays.toString(elements));
    }
}
```

```text:実行結果
[, a, b]
```

## split(String)
これは、 `split(String, int)` の第二引数を `0` にしたのと同じ挙動になる。


# Pattern クラス
## String のメソッドとの違い
一部の例外[^1]を除いて、 `String` クラスの正規表現を使ったメソッドは、裏では `Pattern` クラスに処理を委譲している。
たとえば、 `replaceAll()` メソッドの実装を確認すると次のようになっている。

```java:String.replaceAll()
    public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }
```

Java における正規表現の処理は、この `Pattern` クラス（と `Matcher`）が担当している。

`Pattern` クラスは `compile()` で渡された文字列を正規表現として解釈する。
使用する正規表現が固定なのであれば、この `compile()` は最初の１回だけ実行して、あとは `Pattern` インスタンスを使いまわしたほうが効率がいい。
（`Pattern` クラスはイミュータブルなので、マルチスレッドでも安全に使いまわすことができる）

しかし、 `String` クラスの正規表現を使うメソッドを利用していると、この `compile()` が毎回実行されてしまう。
そのため、固定の正規表現を何度も繰り返し実行するような場合に `String` のメソッドを利用していると、 `Pattern` インスタンスを使いまわすよりも処理速度が遅くなる。

```java:Patternを使いまわす例
public class Hoge {
    // コンパイル済みの Pattern インスタンスを使いまわす
    private static final Pattern HOGE_PATTERN = Pattern.compile("[0-9]+");

    public boolean test(String text) {
        return HOGE_PATTERN.matcher(text).matches(); // 動きは text.maches("[0-9]+") と同じ
    }
}
```

## 基本的な使い方
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[0-9]+");
        
        Matcher abc = pattern.matcher("123abc");
        System.out.println(abc.matches());

        Matcher _123 = pattern.matcher("123");
        System.out.println(_123.matches());
    }
}
```

```text:実行結果
false
true
```

- まず、 `Pattern.compile(String)` で正規表現をコンパイルし、 `Pattern` インスタンスを取得する
- つづいて `Pattern.matcher(String)` メソッドで検証したい文字列（**入力シーケンス**）を渡し、 `Matcher` インスタンスを取得する
- 取得した `Matcher` インスタンスを使って、マッチしたかどうかなどの検証を行う
- `Matcher.matches()` は入力シーケンス全体が正規表現と一致するかどうかを検証し、 `boolean` で結果を返す

## 分割
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.Arrays;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[a-z]+");

        String[] elements = pattern.split("123abc456def789ghi");
        System.out.println(Arrays.toString(elements));

        elements = pattern.split("123abc456def789ghi", -1);
        System.out.println(Arrays.toString(elements));
    }
}
```

```text:実行結果
[123, 456, 789]
[123, 456, 789, ]
```

- `Pattern.split(String)` で、指定した文字列のうち正規表現にマッチする部分で文字列を分割する
- 動きは `String.split(String)`, `String.split(String, int)` と同じ


# Matcher
- `Pattern` は正規表現を解釈するクラスで、次のような処理は `Matcher` が行う
    - 入力シーケンスが正規表現にマッチしているかどうか
    - マッチした部分の抽出
    - マッチした部分の置換
- `Matcher` は、大きく次のようなステップで使用する
    1. マッチ操作を実行する
    2. マッチ操作の結果を問い合わせる
    3. 必要であれば、 1, 2 を繰り返す
- マッチ操作の結果は、以下のメソッドで参照するできる
    - `start()` マッチした入力シーケンス上の開始インデックス
    - `end()` マッチした入力シーケンス上の終了インデックス + 1
    - `group()` マッチした部分文字列
    - マッチ操作を実行していない状態でこれらのメソッドを実行すると、 `IllegalStateException` がスローされる
- `Matcher` はスレッドセーフ**ではない**ので注意


## マッチ操作
`Matcher` には、３つのマッチ操作が存在する。

- `matches()`
    - 入力シーケンス全体と正規表現がマッチするか検証する
- `lookingAt()`
    - 入力シーケンスの先頭から正規表現がマッチするか検証する
- `find()`
    - 入力シーケンスに正規表現にマッチする部分が存在するか順番に検証する

### matches()
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        test("abc");
        test("abc123");
    }

    private static void test(String text) {
        Pattern pattern = Pattern.compile("[a-z]+");
        Matcher matcher = pattern.matcher(text);

        System.out.println("[text=" + text + "]");
        if (matcher.matches()) {
            System.out.println("matches = true");
            System.out.println("start = " + matcher.start());
            System.out.println("end = " + matcher.end());
            System.out.println("group = " + matcher.group());
        } else {
            System.out.println("matches = false");
        }
    }
}
```

```text:実行結果
[text=abc]
matches = true
start = 0
end = 3
group = abc

[text=abc123]
matches = false
```

- `matches()` は、入力シーケンス全体が正規表現とマッチするか検証する
- マッチする場合は `true` を返す

### lookingAt()
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        test("abc");
        test("123abc");
        test("ab12");
    }

    private static void test(String text) {
        Pattern pattern = Pattern.compile("[a-z]+");
        Matcher matcher = pattern.matcher(text);

        System.out.println("[text=" + text + "]");
        if (matcher.lookingAt()) {
            System.out.println("lookingAt = true");
            System.out.println("start = " + matcher.start());
            System.out.println("end = " + matcher.end());
            System.out.println("group = " + matcher.group());
        } else {
            System.out.println("lookingAt = false");
        }
    }
}
```

```text:実行結果
[text=abc]
lookingAt = true
start = 0
end = 3
group = abc

[text=123abc]
lookingAt = false

[text=ab12]
lookingAt = true
start = 0
end = 2
group = ab
```

- `lookingAt()` は、入力シーケンスの先頭から正規表現にマッチするか検証する
- 先頭からの検証結果がマッチしていれば、 `true` が返される（全体が一致している必要はない）

### find()
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        test("abc");
        test("123abc456def789");
    }

    private static void test(String text) {
        Pattern pattern = Pattern.compile("[a-z]+");
        Matcher matcher = pattern.matcher(text);

        System.out.println("[text=" + text + "]");
        while (matcher.find()) {
            System.out.println("start = " + matcher.start());
            System.out.println("end = " + matcher.end());
            System.out.println("group = " + matcher.group());
        }
    }
}
```

```text:実行結果
[text=abc]
start = 0
end = 3
group = abc

[text=123abc456def789]
start = 3
end = 6
group = abc

start = 9
end = 12
group = def
```

- `find()` メソッドは、入力シーケンスの先頭から正規表現にマッチする部分がないか走査する
- マッチする部分文字列が存在した場合は `true` を返す
- `find()` をもう一度実行すると、前回マッチした部分から再びマッチする部分文字列が存在しないか走査が行われる
    - 繰り返し実行することでマッチした部分文字列の抽出が可能
- `start()`, `end()`, `group()` は、直前にマッチした結果が返る

## 置換
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[a-z]+");
        Matcher matcher = pattern.matcher("abc123def");

        System.out.println("replaceAll = " + matcher.replaceAll("*"));
        System.out.println("replaceFirst = " + matcher.replaceFirst("*"));
    }
}
```

```text:実行結果
replaceAll = *123*
replaceFirst = *123def
```

- `Matcher.replaceAll(String)` で、マッチした部分文字列を全て置換する
- `Matcher.replaceFirst(String)` で、最初にマッチした部分文字列だけを置換する

## グループ
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("([a-z]+)([0-9]+)");
        Matcher matcher = pattern.matcher("abc123de45fg");

        int groupCount = matcher.groupCount();
        System.out.println("groupCount=" + groupCount);
        
        while (matcher.find()) {
            System.out.println("==========");
            String group = matcher.group();
            System.out.println("group=" + group);
            
            for (int i=0; i<=groupCount; i++) {
                String g = matcher.group(i);
                System.out.println("group(" + i + ")=" + g);
            }
        }
    }
}
```

```text:実行結果
groupCount=2
==========
group=abc123
group(0)=abc123
group(1)=abc
group(2)=123
==========
group=de45
group(0)=de45
group(1)=de
group(2)=45
```

- 正規表現で定義したグループ（`()` で囲われた部分）について参照するには、以下のメソッドが用意されている
    - `groupCount()` 正規表現で定義されたグループの数を取得する
    - `group()` 直近のマッチ操作でマッチした文字列全体を取得する
    - `group(int)` 直近のマッチ操作でマッチしたグループのうち、指定したインデックスのグループを取得する
        - 番号 `0` はマッチした文字列全体なので、 `group()` と同じ結果を返す
        - `1` から先がマッチした部分文字列になる

### グループに名前をつける
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("(?<alphabets>[a-z]+)(?<numbers>[0-9]+)");
        Matcher matcher = pattern.matcher("abc123de45fg");
        
        while (matcher.find()) {
            System.out.println("==========");
            System.out.println("group(alphabets)=" + matcher.group("alphabets"));
            System.out.println("group(numbers)=" + matcher.group("numbers"));
        }
    }
}
```

```text:実行結果
==========
group(alphabets)=abc
group(numbers)=123
==========
group(alphabets)=de
group(numbers)=45
```

- グループを `(?<グループ名>パターン)` と定義することで、グループに名前を定義することができる
- `group(String)` メソッドで定義した名前を指定してグループにマッチした部分文字列を取得できる

グループ名を置換文字列で参照する場合は、次のようにする

```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("(?<alphabets>[a-z]+)(?<numbers>[0-9]+)");
        Matcher matcher = pattern.matcher("abc123def456");

        String replaced = matcher.replaceAll("${numbers}${alphabets}");
        System.out.println(replaced);
    }
}
```

```text:実行結果
123abc456def
```

- `${グループ名}` でグループを参照できる

# フラグ
- `Pattern` インスタンスを生成するさい、正規表現を解釈する方法を**フラグ**で調整することができる。

```java:フラグを指定したコンパイル
Pattern pattern = Pattern.compile("[a-z]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
```

- フラグは `compile(String, int)` の第二引数で指定する
- 指定できるのは、 `Pattern` クラスに `static` で宣言された定数になる
- ビットマスクになっているので、複数のフラグを指定する場合は `|` で連結して指定する

## CASE_INSENSITIVE （大文字小文字を区別しない）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[a-z]+", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher("ABC");
        System.out.println(matcher.matches());
    }
}
```

```text:実行結果
true
```

- `CASE_INSENSITIVE` を指定した場合、マッチは大文字小文字を区別せずに行われる
- 区別しなくなるのは US-ASCII の文字のみ

## UNICODE_CASE（Unicode で大文字小文字を区別しない）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[a-zＡ-Ｚ]+", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
        Matcher matcher = pattern.matcher("ABCａｂｃ");
        System.out.println(matcher.matches());
    }
}
```

```text:実行結果
true
```

- `UNICODE_CASE` と `CASE_INSENSITIVE` を組み合わせると、 Unicode で大文字小文字の区別なしでマッチングが行われる

## LITERAL（正規表現のメタ文字やエスケープ文字を使わない）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        Pattern pattern = Pattern.compile("[a-z]+", Pattern.LITERAL);
        
        Matcher matcher = pattern.matcher("abc");
        System.out.println(matcher.matches());
        
        matcher = pattern.matcher("[a-z]+");
        System.out.println(matcher.matches());
    }
}
```

```text:実行結果
false
true
```

- `LITERAL` を指定すると、 `compile(String, int)` の第一引数で渡した文字列は単純な文字列として処理される
- `[]` や `+` のような正規表現で意味のある文字は、単純にその文字そのものとして解釈される

## MULTILINE （複数行の文字列を処理する）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        test("[default]", () -> Pattern.compile("^[a-z]+$"));
        test("[MULTILINE]", () -> Pattern.compile("^[a-z]+$", Pattern.MULTILINE));
    }
    
    private static void test(String label, Supplier<Pattern> patternSupplier) {
        System.out.println(label);
        Pattern pattern = patternSupplier.get();

        String text = "abc\n"
                    + "def\n";

        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            String group = matcher.group();
            System.out.println(group);
        }
    }
}
```

```text:実行結果
[default]
[MULTILINE]
abc
def
```

- `MULTILINE` を指定した場合、行頭、行末を表す `^`, `$` の扱いが変化する
- 何も指定していない場合、 `^`, `$` は純粋に文字列の先頭・末尾にのみマッチする
- `MULTILINE` を指定している場合、改行で区切られたそれぞれが文字列として処理されるため、 `^` と `$` は各行の先頭と末尾にマッチするようになる

## COMMENTS （コメントを書けるようにする）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        String regexp = "# この行はコメントとして無視される\n"
                      + "  [a-z]+  ";
        Pattern pattern = Pattern.compile(regexp, Pattern.COMMENTS);

        Matcher matcher = pattern.matcher("abc");

        System.out.println(matcher.matches());
    }
}
```

```text:実行結果
true
```

- `COMMENTS` を指定すると、以下の文字列はコメント扱いになり無視される
    - `#` から行末まで
    - 空白スペース

## DOTALL （`.` で行末にもマッチするようにする）
```java
package sample.regexp;

import org.openjdk.jmh.runner.RunnerException;

import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {

    public static void main(String[] args) throws RunnerException {
        test("[default1]", () -> Pattern.compile(".+"));
        test("[default2]", () -> Pattern.compile(".+$"));
        test("[DOTALL]", () -> Pattern.compile(".+", Pattern.DOTALL));
    }

    private static void test(String label, Supplier<Pattern> patternSupplier) {
        System.out.println(label);
        Pattern pattern = patternSupplier.get();

        String text = "abc\n"
                    + "def\n";

        Matcher matcher = pattern.matcher(text);

        if (matcher.find()) {
            String group = matcher.group();
            System.out.println(group);
        }
    }
}
```

```text:実行結果
[default1]
abc
[default2]
def
[DOTALL]
abc
def
```

- `DOTALL` を指定すると、 `.` が行末にもマッチするようになる
- デフォルトの場合、 `.` は行末にはマッチしない




# 参考
- [Pattern (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/regex/Pattern.html)
- [Matcher (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/regex/Matcher.html)
- [Unicodeに準拠した大文字と小文字を区別しない(UNIX_LINES, (?u)) - オプション修飾子 - Java正規表現の使い方](https://www.javadrive.jp/regex/option/index7.html)
- [パターンのリテラル構文解析を有効にする(LITERAL) - オプション修飾子 - Java正規表現の使い方](https://www.javadrive.jp/regex/option/index8.html)


[^1]: 例えば `split(String regexp)` メソッドは、 `regexp` が正規表現のメタ文字などが使われていない素の文字列だった場合に、 `Pattern` を使わずに分割処理を行っている