1. 序章

多くの場合、 String を操作しているときに、Stringが有効な数値であるかどうかを確認する必要があります。

このチュートリアルでは、指定された文字列が数値であるかどうかを検出する複数の方法を検討します。最初にプレーンJavaを使用し、次に正規表現を使用し、最後に外部ライブラリを使用します。

さまざまな実装について説明したら、ベンチマークを使用して、どのメソッドが最適であるかを判断します。

2. 前提条件

メインコンテンツに進む前に、いくつかの前提条件から始めましょう。

この記事の後半では、Apache Commons外部ライブラリを使用して、pom.xmlに依存関係を追加します。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

このライブラリの最新バージョンは、 MavenCentralにあります。

3. プレーンJavaの使用

おそらく、 String が数値であるかどうかを確認する最も簡単で信頼できる方法は、Javaの組み込みメソッドを使用して解析することです。

  1. Integer.parseInt(String)
  2. Float.parseFloat(String)
  3. Double.parseDouble(String)
  4. Long.parseLong(String)
  5. new BigInteger(String)

これらのメソッドがNumberFormatExceptionをスローしない場合は、解析が成功し、Stringが数値であることを意味します。

public static boolean isNumeric(String strNum) {
    if (strNum == null) {
        return false;
    }
    try {
        double d = Double.parseDouble(strNum);
    } catch (NumberFormatException nfe) {
        return false;
    }
    return true;
}

このメソッドの動作を見てみましょう。

assertThat(isNumeric("22")).isTrue();
assertThat(isNumeric("5.05")).isTrue();
assertThat(isNumeric("-200")).isTrue(); 
assertThat(isNumeric("10.0d")).isTrue();
assertThat(isNumeric("   22   ")).isTrue();
 
assertThat(isNumeric(null)).isFalse();
assertThat(isNumeric("")).isFalse();
assertThat(isNumeric("abc")).isFalse();

isNumeric()メソッドでは、Double型の値をチェックしているだけです。 ただし、このメソッドを変更して、 Integer Float Long 、および以前に参加した解析メソッドのいずれかを使用して多数をチェックすることもできます。

これらのメソッドについては、 Java StringConversionsの記事でも説明されています。

4. 正規表現の使用

次に、正規表現-?\ d +(\。\ d +)?を使用して、正または負の整数と浮動小数点数で構成される数値文字列を照合します。

言うまでもなく、この正規表現を変更して、さまざまなルールを識別して処理することができます。 ここでは、単純にしておきます。

この正規表現を分解して、どのように機能するかを見てみましょう。

  • -? –この部分は、指定された数値が負であるかどうかを識別し、ダッシュ「」は文字通りダッシュを検索し、疑問符「」はその存在を示しますオプションとして
  • \ d + –これは1つ以上の数字を検索します
  • (\。\ d +)? –正規表現のこの部分は、フロート番号を識別するためのものです。 ここでは、1つ以上の数字とそれに続くピリオドを検索しています。 最後に疑問符は、この完全なグループがオプションであることを示します。

正規表現は非常に幅広いトピックです。 簡単な概要については、Java正規表現APIのチュートリアルを確認してください。

今のところ、上記の正規表現を使用してメソッドを作成しましょう。

private Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");

public boolean isNumeric(String strNum) {
    if (strNum == null) {
        return false; 
    }
    return pattern.matcher(strNum).matches();
}

次に、上記のメソッドのいくつかのアサーションを見てみましょう。

assertThat(isNumeric("22")).isTrue();
assertThat(isNumeric("5.05")).isTrue();
assertThat(isNumeric("-200")).isTrue();

assertThat(isNumeric(null)).isFalse();
assertThat(isNumeric("abc")).isFalse();

 5. ApacheCommonsの使用

このセクションでは、ApacheCommonsライブラリで使用できるさまざまなメソッドについて説明します。

5.1.  NumberUtils.isCreatable(String) 

ApacheCommonsのNumberUtilsは、 Stringが有効なJava番号であるかどうかをチェックする静的メソッドNumberUtils.isCreatable(String)、を提供します。

このメソッドは以下を受け入れます:

  1. 0xまたは0Xで始まる16進数
  2. 先行0で始まる8進数
  3. 科学的記数法(例:1.05e-10)
  4. 型修飾子でマークされた番号(たとえば、1Lまたは2.2d)

指定された文字列がnullまたはempty/ blank の場合、数値とは見なされず、メソッドはfalseを返します。

この方法を使用していくつかのテストを実行してみましょう。

assertThat(NumberUtils.isCreatable("22")).isTrue();
assertThat(NumberUtils.isCreatable("5.05")).isTrue();
assertThat(NumberUtils.isCreatable("-200")).isTrue();
assertThat(NumberUtils.isCreatable("10.0d")).isTrue();
assertThat(NumberUtils.isCreatable("1000L")).isTrue();
assertThat(NumberUtils.isCreatable("0xFF")).isTrue();
assertThat(NumberUtils.isCreatable("07")).isTrue();
assertThat(NumberUtils.isCreatable("2.99e+8")).isTrue();
 
assertThat(NumberUtils.isCreatable(null)).isFalse();
assertThat(NumberUtils.isCreatable("")).isFalse();
assertThat(NumberUtils.isCreatable("abc")).isFalse();
assertThat(NumberUtils.isCreatable(" 22 ")).isFalse();
assertThat(NumberUtils.isCreatable("09")).isFalse();

6、7、8行目で、それぞれ16進数、8進数、科学的記数法に対してtrueアサーションを取得していることに注意してください。

また、14行目では、文字列“ 09”false を返します。これは、前の“ 0” がこれが8進数であることを示しており、“ 09”は有効な8進数ではありません。

このメソッドでtrueを返すすべての入力に対して、 NumberUtils.createNumber(String)を使用できます。これにより、有効な数値が得られます。

5.2.  NumberUtils.isParsable(String) 

NumberUtils.isParsable(String)メソッドは、指定されたStringが解析可能かどうかを確認します。

解析可能な数値は、 Integer.parseInt(String) Long.parseLong(String) Float.parseFloat(String)[X177Xなどの解析メソッドによって正常に解析される数値です。 ]またはDouble.parseDouble(String)

NumberUtils.isCreatable()とは異なり、このメソッドは、16進数、科学的記数法、または‘f’、’F’、’d’、’などの任意のタイプの修飾子で終わる文字列を受け入れません。 D’、’l’または‘L’

いくつかの断言を見てみましょう:

assertThat(NumberUtils.isParsable("22")).isTrue();
assertThat(NumberUtils.isParsable("-23")).isTrue();
assertThat(NumberUtils.isParsable("2.2")).isTrue();
assertThat(NumberUtils.isParsable("09")).isTrue();

assertThat(NumberUtils.isParsable(null)).isFalse();
assertThat(NumberUtils.isParsable("")).isFalse();
assertThat(NumberUtils.isParsable("6.2f")).isFalse();
assertThat(NumberUtils.isParsable("9.8d")).isFalse();
assertThat(NumberUtils.isParsable("22L")).isFalse();
assertThat(NumberUtils.isParsable("0xFF")).isFalse();
assertThat(NumberUtils.isParsable("2.99e+8")).isFalse();

4行目では、 NumberUtils.isCreatable()とは異なり、文字列“ 0” で始まる数値は、8進数ではなく、通常の10進数と見なされるため、trueを返します。 。

このメソッドは、セクション3で行った方法の代わりに使用できます。ここでは、数値を解析してエラーをチェックしようとしています。

5.3. StringUtils.isNumeric(CharSequence ) 

メソッドStringUtils.isNumeric(CharSequence)は、Unicode桁を厳密にチェックします。 これの意味は:

  1. Unicode数字である任意の言語の任意の数字が受け入れられます
  2. 小数点はUnicode桁とは見なされないため、無効です
  3. 先行標識(正または負)も受け入れられません

次に、このメソッドの動作を見てみましょう。

assertThat(StringUtils.isNumeric("123")).isTrue();
assertThat(StringUtils.isNumeric("١٢٣")).isTrue();
assertThat(StringUtils.isNumeric("१२३")).isTrue();
 
assertThat(StringUtils.isNumeric(null)).isFalse();
assertThat(StringUtils.isNumeric("")).isFalse();
assertThat(StringUtils.isNumeric("  ")).isFalse();
assertThat(StringUtils.isNumeric("12 3")).isFalse();
assertThat(StringUtils.isNumeric("ab2c")).isFalse();
assertThat(StringUtils.isNumeric("12.3")).isFalse();
assertThat(StringUtils.isNumeric("-123")).isFalse();

2行目と3行目の入力パラメーターは、それぞれアラビア語とデーバナーガリー語の数字123を表していることに注意してください。 これらは有効なUnicode数字であるため、このメソッドはtrueを返します。

5.4. StringUtils.isNumericSpace(CharSequence)

StringUtils.isNumericSpace(CharSequence)は、Unicodeの数字やスペースを厳密にチェックします。 これはStringUtils.isNumeric()と同じですが、スペース、先頭と末尾のスペースだけでなく、数字の間にある場合も受け入れる点が異なります。

assertThat(StringUtils.isNumericSpace("123")).isTrue();
assertThat(StringUtils.isNumericSpace("١٢٣")).isTrue();
assertThat(StringUtils.isNumericSpace("")).isTrue();
assertThat(StringUtils.isNumericSpace("  ")).isTrue();
assertThat(StringUtils.isNumericSpace("12 3")).isTrue();
 
assertThat(StringUtils.isNumericSpace(null)).isFalse();
assertThat(StringUtils.isNumericSpace("ab2c")).isFalse();
assertThat(StringUtils.isNumericSpace("12.3")).isFalse();
assertThat(StringUtils.isNumericSpace("-123")).isFalse();

6. ベンチマーク

この記事を締めくくる前に、いくつかのベンチマーク結果を見て、上記の方法のどれが私たちのユースケースに最適であるかを分析するのに役立てましょう。

6.1. シンプルなベンチマーク

まず、簡単なアプローチを取ります。 1つの文字列値を選択します–テストでは、Integer.MAX_VALUEを使用します。 次に、その値がすべての実装に対してテストされます。

Benchmark                                     Mode  Cnt    Score   Error  Units
Benchmarking.usingCoreJava                    avgt   20   57.241 ± 0.792  ns/op
Benchmarking.usingNumberUtils_isCreatable     avgt   20   26.711 ± 1.110  ns/op
Benchmarking.usingNumberUtils_isParsable      avgt   20   46.577 ± 1.973  ns/op
Benchmarking.usingRegularExpressions          avgt   20  101.580 ± 4.244  ns/op
Benchmarking.usingStringUtils_isNumeric       avgt   20   35.885 ± 1.691  ns/op
Benchmarking.usingStringUtils_isNumericSpace  avgt   20   31.979 ± 1.393  ns/op

ご覧のとおり、最もコストのかかる操作は正規表現です。 その後、私たちのコアJavaベースのソリューションです。

さらに、ApacheCommonsライブラリを使用した操作は概して同じであることに注意してください。

6.2. 強化されたベンチマーク

より代表的なベンチマークのために、より多様なテストのセットを使用してみましょう。

  • 95の値は数値です(0-94および Integer.MAX_VALUE
  • 3には数値が含まれていますが、フォーマットが正しくありません—’ x0 ‘、’ 0. .005’、および’ –11
  • 1にはテキストのみが含まれます
  • 1はnullです

同じテストを実行すると、結果が表示されます。

Benchmark                                     Mode  Cnt      Score     Error  Units
Benchmarking.usingCoreJava                    avgt   20  10162.872 ± 798.387  ns/op
Benchmarking.usingNumberUtils_isCreatable     avgt   20   1703.243 ± 108.244  ns/op
Benchmarking.usingNumberUtils_isParsable      avgt   20   1589.915 ± 203.052  ns/op
Benchmarking.usingRegularExpressions          avgt   20   7168.761 ± 344.597  ns/op
Benchmarking.usingStringUtils_isNumeric       avgt   20   1071.753 ±   8.657  ns/op
Benchmarking.usingStringUtils_isNumericSpace  avgt   20   1157.722 ±  24.139  ns/op

最も重要な違いは、正規表現ソリューションとコアJavaベースのソリューションの2つのテストが場所を交換したことです。

この結果から、5% oの場合にのみ発生するNumberFormatException のスローと処理が、全体的なパフォーマンスに比較的大きな影響を与えることがわかります。 したがって、最適なソリューションは、予想される入力に依存すると結論付けることができます。

また、最適なパフォーマンスを得るには、Commonsライブラリのメソッドまたは同様に実装されたメソッドを使用する必要があると安全に結論付けることができます。

7. 結論

この記事では、Stringが数値であるかどうかを確認するさまざまな方法を検討しました。 組み込みメソッドと外部ライブラリの両方のソリューションを検討しました。

いつものように、ベンチマークの実行に使用されるコードを含む、上記のすべての例とコードスニペットの実装は、GitHubにあります。