Java Tips | 文字列処理:数字抽出

Java Java
スポンサーリンク

数字抽出は「文章の中から“数値だけ”をすくい上げる」技

業務システムでは、ログメッセージ、メール本文、外部システムからのテキスト、Excel から吐き出された中途半端な文字列など、「文字と数字がごちゃ混ぜになったデータ」が頻繁に登場します。
そこから「金額だけ欲しい」「ID の数字部分だけ欲しい」「文章中に出てくるすべての数値を集めたい」といったニーズが出てきます。

このときに役立つのが「数字抽出」のユーティリティです。
ポイントは、「どの単位で数字を取りたいか(1桁ずつか、連続した数字を1つの数としてか)」「負号や小数点をどう扱うか」を最初に決めておくことです。


基本の考え方:「連続した数字の塊」を1つの数として扱う

正規表現で「数字のかたまり」を見つける

まずは一番よくある、「連続した数字を1つの数として扱う」パターンからいきます。
例えば、次のような文字列があるとします。

注文ID: ORD-20250130-001 金額: 12345 円(税抜)

ここから「20250130」「001」「12345」を取り出したい、というイメージです。

Java では、正規表現 \\d+ を使うと「1桁以上の連続した数字」にマッチできます。
これを Matcher#find() で繰り返し拾っていくのが基本パターンです。


連続した数字をすべて抽出するユーティリティ

文字列中の「数字の塊」を List<String> で返す

まずは、「文字列中に出てくる数字の塊を全部文字列として集める」ユーティリティを作ります。

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

public final class NumberExtractor {

    private static final Pattern DIGITS = Pattern.compile("\\d+");

    private NumberExtractor() {}

    public static List<String> extractAllDigitBlocks(String text) {
        List<String> result = new ArrayList<>();
        if (text == null || text.isEmpty()) {
            return result;
        }
        Matcher m = DIGITS.matcher(text);
        while (m.find()) {
            result.add(m.group());
        }
        return result;
    }
}
Java

使い方はこうなります。

String s = "注文ID: ORD-20250130-001 金額: 12345 円(税抜)";
List<String> nums = NumberExtractor.extractAllDigitBlocks(s);

System.out.println(nums); // [20250130, 001, 12345]
Java

ここで深掘りしたい重要ポイントは、「matches() ではなく find() をループしている」ことです。
matches() は「文字列全体がパターンに一致するか」を見るのに対し、
find() は「文字列の中から、パターンに一致する部分を順番に見つけていく」ためのメソッドです。

数字抽出のように「文章の中から何度も出てくる数字を全部拾いたい」場合は、find() のループが基本形になります。


抽出した数字を int / long に変換する

「文字列としての数字」から「数値として扱える形」へ

業務では、「数字を文字列として表示したい」だけでなく、「合計したい」「大小比較したい」といったニーズも出てきます。
そのため、「抽出した数字を intlong に変換する」ユーティリティもセットで用意しておくと便利です。

import java.util.ArrayList;
import java.util.List;

public final class NumberExtractor {

    // 先ほどの DIGITS と extractAllDigitBlocks は省略

    public static List<Long> extractAllLongs(String text) {
        List<String> blocks = extractAllDigitBlocks(text);
        List<Long> result = new ArrayList<>(blocks.size());
        for (String b : blocks) {
            try {
                result.add(Long.parseLong(b));
            } catch (NumberFormatException e) {
                // 桁数が大きすぎるなどでパースできない場合はスキップする方針
            }
        }
        return result;
    }
}
Java

使い方はこうです。

String s = "ID: 001, 金額: 12345, 在庫: 9999999999";
List<Long> values = NumberExtractor.extractAllLongs(s);

System.out.println(values); // [1, 12345, 9999999999]
Java

ここでのポイントは、「パースに失敗したときの方針をユーティリティ側で決めている」ことです。
上の例では、「Long.parseLong に失敗したらその数字はスキップする」というルールにしていますが、
「例外をそのまま投げる」「null を入れる」「別のリストにエラーとして集める」など、プロジェクトの方針に合わせて決めておくとよいです。


例題:文章中の「最初の数字だけ」を取りたい

「一番最初に出てくる数字」を 1つだけ返す

よくあるのが、「文章中の最初の数字だけ欲しい」というパターンです。
例えば、「エラーメッセージからエラーコードだけ取りたい」といったケースです。

public final class NumberExtractor {

    // DIGITS は前と同じ

    public static String extractFirstDigitBlock(String text) {
        if (text == null || text.isEmpty()) {
            return null;
        }
        Matcher m = DIGITS.matcher(text);
        if (m.find()) {
            return m.group();
        }
        return null;
    }

    public static Long extractFirstLong(String text) {
        String block = extractFirstDigitBlock(text);
        if (block == null) {
            return null;
        }
        try {
            return Long.parseLong(block);
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
Java

使い方はこうです。

String msg = "エラーコード[105]:接続に失敗しました。";
Long code = NumberExtractor.extractFirstLong(msg);

System.out.println(code); // 105
Java

ここで深掘りしたいのは、「“最初の1つだけ”という要件をメソッド名と戻り値の型で表現している」ことです。
extractAll... は List、extractFirst... は単一値(または null)という形にしておくと、
呼び出し側が「このメソッドは何個返してくるのか」を直感的に理解できます。


マイナスや小数点を含む数値の抽出

「-123.45」のような形式を1つの数として扱う

もう少し踏み込んで、「負号や小数点を含む数値」を抽出したい場合もあります。
例えば、「温度: -12.5 度」「金利: 1.25%」のようなケースです。

簡易的には、次のような正規表現で「数値っぽいもの」を拾えます。

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

public final class DecimalExtractor {

    // 例: -12, 3.14, -0.5 などを対象にする簡易パターン
    private static final Pattern NUMBER_PATTERN =
            Pattern.compile("-?\\d+(?:\\.\\d+)?");

    private DecimalExtractor() {}

    public static List<String> extractAllNumbers(String text) {
        List<String> result = new ArrayList<>();
        if (text == null || text.isEmpty()) {
            return result;
        }
        Matcher m = NUMBER_PATTERN.matcher(text);
        while (m.find()) {
            result.add(m.group());
        }
        return result;
    }
}
Java

使い方はこうです。

String s = "気温: -12.5 度、昨日は 3.0 度、最高 10 度";
System.out.println(DecimalExtractor.extractAllNumbers(s));
// [-12.5, 3.0, 10]
Java

ここでの重要ポイントは、「どこまでを“数値として許容するか”を正規表現で決めている」ことです。
上のパターンでは、「先頭に任意の -」「1桁以上の数字」「任意の小数部(. に続く1桁以上の数字)」というルールにしています。

実務では、「+ を許可するか」「指数表記(1.2e3)を許可するか」など、要件に応じてパターンを調整していきます。
大事なのは、「プロジェクト内で“数値として認める形”をユーティリティとして固定する」ことです。


例題:金額だけを抜き出して合計する

「文章中の金額を全部足し上げる」小さな実務パターン

例えば、こんなテキストがあるとします。

商品A: 1200円, 商品B: 3500円, 送料: 500円

ここから金額だけを抜き出して合計したい、というケースです。

public final class Amounts {

    private Amounts() {}

    public static long sumAllAmounts(String text) {
        List<Long> nums = NumberExtractor.extractAllLongs(text);
        long sum = 0L;
        for (Long n : nums) {
            if (n != null) {
                sum += n;
            }
        }
        return sum;
    }
}
Java

使い方はこうです。

String s = "商品A: 1200円, 商品B: 3500円, 送料: 500円";
long total = Amounts.sumAllAmounts(s);

System.out.println(total); // 5200
Java

ここでのポイントは、「“数字抽出”と“業務ロジック(合計)”をきれいに分離している」ことです。
NumberExtractor はあくまで「数字を拾う」だけに責務を絞り、
Amounts は「拾った数字をどう解釈するか(ここでは全部足す)」という業務寄りの責務を持っています。

この分離を意識しておくと、「数字の取り方を変えたい」「合計ではなく平均を出したい」といった変更にも柔軟に対応できます。


まとめ:数字抽出ユーティリティで身につけたい感覚

数字抽出は、「文字と数字が混ざった世界から、“数値として意味のある部分”だけをすくい上げる」技です。

押さえておきたい感覚は、まず「連続した数字の塊を \\d+ で拾い、Matcher#find() のループで全部集める」という基本パターン。
次に、「extractAll...extractFirst... を分け、戻り値の型(List か単一値か)で“何個取るつもりか”を表現する」こと。
そして、「負号や小数点を含む数値を扱いたい場合は、“どこまでを数値とみなすか”を正規表現で明示し、そのルールをユーティリティとしてプロジェクト全体で共有する」ことです。

もしあなたのコードのどこかに、text.replaceAll("\\D", "") のように「非数字を全部消して数字だけ残す」といった処理が散らばっているなら、
それを題材にして、ここで作った NumberExtractorDecimalExtractor に置き換えてみてください。
それだけで、「読みやすくて、再利用できて、仕様変更にも強い数字抽出」に、一段レベルアップできます。

タイトルとURLをコピーしました