数値形式チェックは「パースする前に落とすためのフィルタ」
業務システムでは、画面から入力された文字列を
「整数として扱いたい」「金額として扱いたい」「小数として扱いたい」
という場面が山ほどあります。
ここで何もチェックせずに Integer.parseInt や BigDecimal に渡すと、abc や 123a が混ざっていた瞬間に NumberFormatException で落ちます。
数値形式チェックの役割は、まさにここです。
「この文字列は“数値としてパースしてよさそうか”を事前に判定し、
おかしなものは早めにはじく」ためのフィルタだと思ってください。
まずは「どんな数値を許したいか」を決める
整数だけか、小数もありか、マイナスは許すか
数値形式チェックを考えるとき、いきなり正規表現を書き始めるのは危険です。
先に決めるべきなのは、「どんな数値を許したいか」です。
例えば、こんなパターンがあります。
「年齢」「個数」「在庫数」など
→ 0以上の整数だけ(小数・マイナスはNG)
「金額」「売上」「単価」など
→ 小数あり(ただし桁数制限あり)、マイナスは基本NG
「温度」「差分」「増減」など
→ 小数あり、マイナスもOK
用途ごとに“許したい数値の範囲・形”が違うので、
「とりあえず全部 double にパースできればOK」
という発想は、実務ではかなり危険です。
ここではまず、
「整数チェック」と「小数チェック」に分けて考えていきます。
整数形式チェックの基本
Java標準のパースをそのまま使うシンプル版
一番素直なやり方は、
「実際に Integer.parseInt を呼んでみて、例外が出るかどうかで判定する」方法です。
public final class NumberValidator {
private NumberValidator() {}
public static boolean isInteger(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
Integer.parseInt(text);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
Java使い方はこうなります。
System.out.println(NumberValidator.isInteger("123")); // true
System.out.println(NumberValidator.isInteger("-5")); // true
System.out.println(NumberValidator.isInteger("0")); // true
System.out.println(NumberValidator.isInteger("")); // false
System.out.println(NumberValidator.isInteger(" 123 ")); // false(空白がある)
System.out.println(NumberValidator.isInteger("12.3")); // false
System.out.println(NumberValidator.isInteger("123a")); // false
Javaここでの重要ポイントは二つです。
一つ目は、「“形式チェック”と“実際のパース”を同じロジックに任せている」ことです。Integer.parseInt が受け付ける形式=「整数として扱える形式」なので、
それをそのまま使うのは理にかなっています。
二つ目は、「空白を含む文字列はこのままだとNGになる」ことです。" 123 " のような入力を許したい場合は、
事前に trim() するかどうかを、ポリシーとして決める必要があります。
整数形式チェックを「0以上」に絞る
年齢・個数・在庫数などの“負の値を許さない”ケース
多くの業務項目は、「マイナスの値を許さない」ものが多いです。
年齢、個数、在庫数、ページ番号など。
その場合は、「整数かどうか」に加えて「0以上かどうか」も見ます。
public final class NumberValidator {
private NumberValidator() {}
public static boolean isNonNegativeInteger(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
int value = Integer.parseInt(text);
return value >= 0;
} catch (NumberFormatException e) {
return false;
}
}
}
Java使い方はこうです。
System.out.println(NumberValidator.isNonNegativeInteger("10")); // true
System.out.println(NumberValidator.isNonNegativeInteger("0")); // true
System.out.println(NumberValidator.isNonNegativeInteger("-1")); // false
System.out.println(NumberValidator.isNonNegativeInteger("abc")); // false
Javaここでのポイントは、「“形式”と“値の範囲”をセットで見る」ことです。
形式だけ見て -1 を通してしまうと、
「年齢に -1 が入っている」といったおかしなデータがDBに残ります。
数値形式チェックは、“パースできるか”だけでなく、“その用途として妥当な範囲か”まで含めて考える
という感覚を持っておくと、実務レベルのバリデーションに近づきます。
小数形式チェックの基本(BigDecimalベース)
金額やレートなど「精度が大事な数値」の扱い
金額やレートなど、精度が大事な数値は、double ではなく BigDecimal で扱うのが定番です。
形式チェックも、BigDecimal のパースに任せるのが素直です。
import java.math.BigDecimal;
public final class DecimalValidator {
private DecimalValidator() {}
public static boolean isDecimal(String text) {
if (text == null || text.isBlank()) {
return false;
}
try {
new BigDecimal(text);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
Java使い方はこうなります。
System.out.println(DecimalValidator.isDecimal("123")); // true
System.out.println(DecimalValidator.isDecimal("123.45")); // true
System.out.println(DecimalValidator.isDecimal("-0.5")); // true
System.out.println(DecimalValidator.isDecimal("123.")); // false(BigDecimalはNG)
System.out.println(DecimalValidator.isDecimal(".5")); // false
System.out.println(DecimalValidator.isDecimal("1,234")); // false(カンマ付きはNG)
System.out.println(DecimalValidator.isDecimal("abc")); // false
Javaここで深掘りしたいポイントは三つです。
一つ目は、「BigDecimal が許す形式=“実務で扱いやすい数値文字列”に近い」ことです。123.45 や -0.5 はOKですが、123. や .5 のような曖昧な表現はNGになります。
これにより、「人間が普通に書く数値」に自然と寄せられます。
二つ目は、「カンマ付き(1,234)はこのままだとNGになる」ことです。
カンマを許したい場合は、事前にカンマを除去する“正規化”処理を挟む必要があります。
三つ目は、「小数の桁数制限は別途見る必要がある」ことです。
例えば「金額は小数第2位まで」といった制約は、BigDecimal にパースしたあとでスケール(小数桁数)を見て判定します。
小数の桁数を制限する(例:小数第2位まで)
金額や税率などでよくある要件
「金額は小数第2位まで」「税率は小数第3位まで」など、
小数の桁数に制限があるケースはよくあります。
BigDecimal にパースしたあとで、scale() を使って判定できます。
import java.math.BigDecimal;
public final class DecimalValidator {
private DecimalValidator() {}
public static boolean isDecimalWithScale(String text, int maxScale) {
if (text == null || text.isBlank()) {
return false;
}
try {
BigDecimal bd = new BigDecimal(text);
return bd.scale() <= maxScale;
} catch (NumberFormatException e) {
return false;
}
}
}
Java使い方はこうです。
// 小数第2位までOK
System.out.println(DecimalValidator.isDecimalWithScale("123", 2)); // true(scale=0)
System.out.println(DecimalValidator.isDecimalWithScale("123.4", 2)); // true(scale=1)
System.out.println(DecimalValidator.isDecimalWithScale("123.45", 2)); // true(scale=2)
System.out.println(DecimalValidator.isDecimalWithScale("123.456", 2)); // false(scale=3)
Javaここでのポイントは、「“形式チェック”と“桁数チェック”を分けて考えつつ、同じメソッドにまとめている」ことです。new BigDecimal(text) が形式チェック、bd.scale() <= maxScale が桁数チェック、
という二段階を一つのメソッドに閉じ込めています。
正規表現で「整数っぽいか」を軽く見る方法
例外を使いたくない場面向けの“軽量版”
「例外を使った判定は重いから避けたい」という場面では、
正規表現で「整数っぽいか」を軽く見る方法もあります。
例えば、「マイナス記号付きの整数」を許すなら、こう書けます。
import java.util.regex.Pattern;
public final class NumberValidatorRegex {
private NumberValidatorRegex() {}
private static final Pattern INTEGER_PATTERN =
Pattern.compile("^-?\\d+$");
public static boolean isIntegerByRegex(String text) {
if (text == null || text.isBlank()) {
return false;
}
return INTEGER_PATTERN.matcher(text).matches();
}
}
Java使い方はこうです。
System.out.println(NumberValidatorRegex.isIntegerByRegex("123")); // true
System.out.println(NumberValidatorRegex.isIntegerByRegex("-5")); // true
System.out.println(NumberValidatorRegex.isIntegerByRegex("12.3")); // false
System.out.println(NumberValidatorRegex.isIntegerByRegex("1 2 3")); // false
System.out.println(NumberValidatorRegex.isIntegerByRegex("abc")); // false
Javaここでの重要ポイントは、「正規表現は“形式”しか見ていない」ことです。-99999999999999999999 のような、int に収まらない値も true になります。
実際に int として扱えるかどうかは、
結局 Integer.parseInt などで別途チェックする必要があります。
つまり、「正規表現だけで完結させよう」とは思わないこと。
形式チェック+実際のパース、という二段構えを意識しておくのが安全です。
例題:入力フォームでの数値形式チェック
例えば、「年齢」を入力してもらうフォームを考えます。
サーバ側では、こんな感じで使えます。
String ageText = request.getParameter("age");
if (ageText == null || ageText.isBlank()) {
errors.add("年齢を入力してください。");
} else if (!NumberValidator.isNonNegativeInteger(ageText.trim())) {
errors.add("年齢は0以上の整数で入力してください。");
} else {
int age = Integer.parseInt(ageText.trim());
if (age > 120) {
errors.add("年齢が不正です。");
}
}
Javaここでのポイントは三つです。
一つ目は、「未入力チェック」「形式チェック」「値の範囲チェック」を段階的に行っている」ことです。
どこで何を見ているかが明確なので、後から読んでも理解しやすくなります。
二つ目は、「形式チェックに通ったあとで、安心して parseInt している」ことです。isNonNegativeInteger が true なら、NumberFormatException は起きない前提で書けます。
三つ目は、「ビジネスルール(120歳まで)を、形式チェックとは別に書いている」ことです。
形式チェックはあくまで「数値として成立しているか」、
ビジネスルールは「そのシステムとして妥当な値か」、
という役割分担を意識しておくと、設計がきれいになります。
まとめ:数値形式チェックユーティリティで身につけたい感覚
数値形式チェックは、「パースできるかどうか」だけを見るのではなく、
「その用途として妥当な形・範囲かどうか」をセットで考えるフィルタ です。
整数ならisInteger(形式)isNonNegativeInteger(0以上)
小数ならisDecimal(形式)isDecimalWithScale(小数桁数制限)
といったメソッドを用意して、
「用途ごとにどれを使うか」を決めていくイメージを持ってください。
もしあなたのコードのどこかに、
int age = Integer.parseInt(request.getParameter("age"));
Javaのような行がそのまま書かれていたら、
そこを題材にして、ここで作った NumberValidator.isNonNegativeInteger を一度挟んでみてください。
それだけで、「おかしな文字列が原因で落ちる」「あり得ない値がDBに残る」といった事故を、かなり減らすことができます。
