数字抽出は「文章の中から“数値だけ”をすくい上げる」技
業務システムでは、ログメッセージ、メール本文、外部システムからのテキスト、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 に変換する
「文字列としての数字」から「数値として扱える形」へ
業務では、「数字を文字列として表示したい」だけでなく、「合計したい」「大小比較したい」といったニーズも出てきます。
そのため、「抽出した数字を int や long に変換する」ユーティリティもセットで用意しておくと便利です。
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", "") のように「非数字を全部消して数字だけ残す」といった処理が散らばっているなら、
それを題材にして、ここで作った NumberExtractor や DecimalExtractor に置き換えてみてください。
それだけで、「読みやすくて、再利用できて、仕様変更にも強い数字抽出」に、一段レベルアップできます。
