1. 소개

종종 String s에서 작업하는 동안 String이 유효한 숫자인지 여부 를 파악해야 합니다 .

이 사용방법(예제)에서는 먼저 일반 Java를 사용한 다음 정규식을 사용하고 마지막으로 외부 라이브러리를 사용하여 주어진 문자열이 숫자인지 감지하는 여러 가지 방법을 탐색합니다 .

다양한 구현에 대한 논의가 끝나면 벤치마크를 사용하여 어떤 방법이 최적인지 파악합니다.

2. 전제 조건

주요 콘텐츠로 이동하기 전에 몇 가지 전제 조건부터 시작하겠습니다.

이 문서의 후반부에서는 Apache Commons 외부 라이브러리를 사용하여 pom.xml 에 의존성을 추가합니다 .

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

이 라이브러리의 최신 버전은 Maven Central 에서 찾을 수 있습니다 .

3. 일반 자바 사용

문자열이 숫자인지 여부 를 확인하는 가장 쉽고 신뢰할 수 있는 방법은 Java의 기본 제공 메서드를 사용하여 문자열을 구문 분석하는 것입니다.

  1. Integer.parseInt(문자열)
  2. Float.parseFloat(문자열)
  3. Double.parseDouble(문자열)
  4. Long.parseLong(문자열)
  5. 새로운 BigInteger(문자열)

이러한 메서드가 NumberFormatException 을 발생시키지 않으면 구문 분석이 성공했고 문자열 이 숫자라는 의미입니다 .

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 문자열 변환 문서 에서도 설명합니다  .

4. 정규 표현식 사용

이제 정규식 -?\d+(\.\d+)? 양수 또는 음수 정수와 실수로 구성된 숫자 문자열을 일치시킵니다 .

광범위한 규칙을 식별하고 처리하기 위해 이 정규식을 확실히 수정할 수 있다는 것은 말할 필요도 없습니다. 여기서는 간단하게 유지하겠습니다.

이 정규식을 분석하고 작동 방식을 살펴보겠습니다.

  • -?  – 이 부분은 주어진 숫자가 음수인지 여부를 식별하고 대시 " "는 문자 그대로 대시를 검색하고 물음표 " ? ” 옵션으로 그 존재를 표시합니다.
  • \d+  – 하나 이상의 숫자를 검색합니다.
  • (\.\d+)? – 정규식의 이 부분은 float 숫자를 식별하는 것입니다. 여기서 우리는 하나 이상의 숫자와 마침표를 검색합니다. 결국 물음표는 이 완전한 그룹이 선택 사항임을 나타냅니다.

정규식은 매우 광범위한 주제입니다. 간략한 개요를 보려면 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. 아파치 커먼즈 사용

이 섹션에서는 Apache Commons 라이브러리에서 사용할 수 있는 다양한 방법에 대해 설명합니다.

5.1. NumberUtils.isCreatable(문자열) 

Apache Commons의 NumberUtils는 정적 메서드 NumberUtils.isCreatable(String) 을 제공 하여 문자열이 유효한 Java 번호인지여부를 확인합니다

이 방법은 다음을 허용합니다.

  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진수 및 과학 표기법에 대한 진정한 어설션을 얻고 있음에 유의하십시오 .

또한 14행에서 문자열 "09"는 앞의 "0"이 이것이 8진수임을 나타내고 "09" 는 유효한 8진수가 아니기 때문에 false를 반환합니다 .

이 메서드로 true를 반환하는 모든 입력에 대해 유효한 숫자를 제공하는 NumberUtils.createNumber(String) 를 사용할 수 있습니다 .

5.2. NumberUtils.isParsable(문자열) 

NumberUtils.isParsable (String) 메서드는 주어진 문자열이 구문 분석 가능한지 여부를 확인합니다.

구문 분석 가능한 숫자는 Integer.parseInt(String) , Long.parseLong(String) , Float.parseFloat(String) 또는 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) 메서드는 유니코드 숫자를 엄격하게 검사합니다. 이는 다음을 의미합니다.

  1. 유니코드 숫자인 모든 언어의 모든 숫자가 허용됩니다.
  2. 소수점은 유니코드 숫자로 간주되지 않으므로 유효하지 않습니다.
  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 을 나타냅니다 . 유효한 유니코드 숫자이므로 이 메서드는 true를 반환합니다 .

5.4. StringUtils.isNumericSpace(CharSequence)

StringUtils.isNumericSpace  (CharSequence)는 유니코드 숫자 및/또는 공백을 엄격하게 검사합니다. 이는 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. 간단한 벤치마크

먼저 간단한 접근 방식을 취합니다. 하나의 문자열 값을 선택합니다. 테스트를 위해 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 기반 솔루션입니다.

또한 Apache Commons 라이브러리를 사용하는 작업은 대체로 동일합니다.

6.2. 향상된 벤치마크

보다 대표적인 벤치마크를 위해 보다 다양한 테스트 세트를 사용해 보겠습니다.

  • 95개의 값은 숫자입니다(0-94 및 Integer.MAX_VALUE ).
  • 3은 숫자를 포함하지만 여전히 잘못된 형식입니다 — ' x0 ', ' 0. .005′ 및 ' –11 '
  • 1은 텍스트만 포함
  • 1은

동일한 테스트를 실행하면 결과가 표시됩니다.

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 기반 솔루션이 자리를 바꿨다는 것입니다.

이 결과로부터 우리는 5%의 경우에만 발생하는 NumberFormatException 의 throw 및 처리가 전체 성능에 상대적으로 큰 영향을 미친다는 것을 알 수 있습니다. 따라서 최적의 솔루션은 예상되는 입력에 따라 다르다는 결론을 내릴 수 있습니다.

또한 최적의 성능을 위해 Commons 라이브러리의 메서드 또는 유사하게 구현된 메서드를 사용해야 한다고 안전하게 결론을 내릴 수 있습니다.

7. 결론

이 기사에서는 문자열이 숫자인지 여부를 찾는 다양한 방법을 살펴보았습니다 . 우리는 내장 방법과 외부 라이브러리의 두 가지 솔루션을 모두 살펴보았습니다.

항상 그렇듯이 벤치마크를 수행하는 데 사용되는 코드를 포함하여 위에 제공된 모든 예제 및 코드 스니펫의 구현은 GitHub 에서 찾을 수 있습니다 .

res – REST with Spring (eBook) (everywhere)