1. 導入:なぜ正規表現だけではダメなのか?
Java開発において、文字列のパターンマッチングに「正規表現(java.util.regex)」は欠かせないツールです。しかし、正規表現には「入れ子構造(ネスト)」を扱うのが非常に苦手という弱点があります。例えば、括弧の中にさらに括弧があるような「( ( ) )」という文字列の解析です。本記事では、なぜ正規表現でこれが難しいのか、そしてJavaでこれを解決するための現実的なアプローチを解説します。
2. 基礎知識:正規表現の限界と再帰パターン
正規表現は「正規言語」という数学的な定義に基づいています。理論上、正規表現は「有限オートマトン」という仕組みで動いており、メモリ(スタック)を持ちません。そのため、「括弧がいくつ開いたか」を記憶しておくことができず、理論的に「任意の深さの入れ子」を判定することができません。
多くのプログラミング言語にある正規表現エンジンは拡張されており、一部の言語(PHPのPCREなど)は「再帰パターン」をサポートしていますが、Javaのjava.util.regexは再帰パターンをサポートしていません。そのため、Javaで入れ子構造を扱うには、正規表現を捨てて「スタック」を用いたアルゴリズムを採用するのが正攻法です。
3. 実装/解決策:スタックを使った解決法
入れ子構造を解析する最も確実な方法は「スタック」を使うことです。
・左括弧「(」を見つけたらスタックに積む
・右括弧「)」を見つけたらスタックから一つ取り出す
・解析終了時にスタックが空であれば「正しく閉じている」と判定する
この方法なら、どれだけ深い入れ子であっても問題なく処理できます。
4. サンプルプログラム
以下のコードは、文字列が正しく括弧で閉じられているかを判定する実用的なサンプルです。
import java.util.Stack;
public class ParenthesesChecker {
public static void main(String[] args) {
String input = "((Java) is (fun))";
System.out.println("判定結果: " + isBalanced(input));
}
public static boolean isBalanced(String str) {
// 文字列の括弧を追跡するためのスタック
Stack<Character> stack = new Stack<>();
for (char c : str.toCharArray()) {
if (c == '(') {
// 左括弧ならスタックに積む
stack.push(c);
} else if (c == ')') {
// 右括弧の場合、スタックが空なら不正(開き括弧がない)
if (stack.isEmpty()) {
return false;
}
// 対応する開き括弧を取り出す
stack.pop();
}
}
// 最後にスタックが空であれば、すべて正しく閉じられている
return stack.isEmpty();
}
}
5. 応用・注意点:現場での判断基準
正規表現で解決しようとしないことが、最も重要な教訓です。無理に入れ子を正規表現で書こうとすると、非常に複雑で保守不可能なコードになり、特定の深さまでしか対応できない不完全なツールが出来上がります。
もし、「括弧の中身を抽出したい」といった複雑なタスクが必要な場合は、正規表現ではなく「パーサー(Parser)」を自作するか、Javaのライブラリ(ANTLRなど)を利用することを検討してください。単純なチェックであれば、上記のスタックアルゴリズムで十分高速かつ正確に動作します。まずは、道具の適材適所を見極めることが、シニアエンジニアへの第一歩です。

コメント