원본 글: https://www.baeldung.com/java-check-string-number
개발을 하다보면 String객체에서 유효한 숫자나 그렇지 않은 값을 추출해내야할 때가 있습니다.
이 글은 String객체의 값이 숫자형태를 지니고 있는지 판단하는 여러 방법들에 대해 논의합니다. Java코드만을 이용한 방법과 정규표현식, 그리고 외부 라이브러리를 통한 방법까지 살펴봅니다.
다양한 방법들에 대한 설명이 마무리되면 벤치마킹을 통해 어떤 방법이 성능적으로 좋은지 판단해보겠습니다.
1. Prerequisites
이 글에서는 Apache Commons 라이브러리를 사용하므로 관련 의존성을 추가해줘야합나다.
testImplementation 'org.apache.commons:commons-lang3:3.17.0'
2. Using Plain Java
String가 지닌값이 숫자인지 확인하는 방법중 가장 쉽고 신뢰가는 방법은 Java가 제공하는 함수들을 사용하는 것일 수 있습니다.
- Integer.parseInt(String)
- Float.parseFloat(String)
- Double.parseDouble(String)
- Long.parseLong(String)
- new BigInteger(String)
위 함수들이 NumberFormatException
을 발생시키지 않는다면 정상적으로 동작했음을 알 수 있습니다.
@Test
public void plain_java_test() {
assertTrue(isNumeric("22"));
assertTrue(isNumeric("5.05"));
assertTrue(isNumeric("-200"));
assertTrue(isNumeric("10.0d"));
assertTrue(isNumeric(" 22 "));
assertFalse(isNumeric(null));
assertFalse(isNumeric(""));
assertFalse(isNumeric("abc "));
}
public static boolean isNumeric(String s) {
if(s==null || s.length()==0) return false;
try {
double d = Double.parseDouble(s);
} catch (NumberFormatException e) {
return false;
}
return true;
}
위 예제는 Dobule 타입을 이용해서 값이 숫자인지 확인합니다. 또한, Integer, Float, Long 등에 대한 함수들로 수정하여 원하는 정보를 확인할 수 있습니다.
3. Using Regular Expressions
-?\d+(\.\d+)?
라는 정규표현식을 통해서 String값이 양수인지 음수인지 또는 정수인지 소수점을 지니고 있는지 확인할 수 있습니다.
간단하게 구성하였고 어떻게 동작하는지 확인해보겠습니다.
-?
: '-' 문자가 있을수도 있고 없을수도 있습니다.\d+
: \d -> 하나 이상의 숫자를 찾습니다. / + -> 최소 1개이상의 연속된 숫자를 의미합니다.(\.\d+)?
: . -> 소수점을 포함한 숫자를 찾습니다. \d-> 소수점 뒤에 최소 1개 이상의 숫자가 포함됩니다. ()? -> 괄호안의 값은 있어도 되고 없어도 됩니다.
@Test
public void regular_expression_java_test() {
assertTrue(isNumericByRegex("22"));
assertTrue(isNumericByRegex("5.05"));
assertTrue(isNumericByRegex("-200"));
assertFalse(isNumericByRegex(null));
assertFalse(isNumericByRegex(""));
assertFalse(isNumericByRegex("abc "));
}
public static boolean isNumericByRegex(String s) {
Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");
if(s==null || s.length()==0) return false;
return pattern.matcher(s).matches();
}
4. Using Apache Commons
외부라이브러리인 Apache Commons를 사용해서 확인하는 방법을 알아보겠습니다.
4.1 NumberUtils.isCreatable(String)
NumberUtils라는 클래스의 isCreatable() 함수를 통해서 유효한 숫자값인지 확인이 가능합니다.
이 함수가 판단하는 기준은 다음과 같습니다.
- 16진수는 0x 또는 0X로 시작해야합니다.
- 8진수는 0으로 시작해야합니다.
- 과학적 표기법 지원합니다.(1.05e-10 같은 것)
- 타입 한정자로 표시된 숫자 식별가능합니다(1L, 2.2d 같은것)
Apache Commons 라이브러리답게 null과 빈 문자열에 대해서는 따로 처리를 안해줘도 됩니다. 해당 값이 들어올 경우 false
를 리턴합니다.
@Test
public void apache_commons_iscreatable_test(){
assertTrue(NumberUtils.isCreatable("22"));
assertTrue(NumberUtils.isCreatable("5.05"));
assertTrue(NumberUtils.isCreatable("-200"));
assertTrue(NumberUtils.isCreatable("10.0d"));
assertTrue(NumberUtils.isCreatable("1000L"));
assertTrue(NumberUtils.isCreatable("0xFF"));
assertTrue(NumberUtils.isCreatable("07"));
assertTrue(NumberUtils.isCreatable("2.99e+8"));
assertFalse(NumberUtils.isCreatable(null));
assertFalse(NumberUtils.isCreatable(""));
assertFalse(NumberUtils.isCreatable("abc"));
assertFalse(NumberUtils.isCreatable(" 22 "));
assertFalse(NumberUtils.isCreatable("09")); //8진수 벗어남.
}
4.2 NumberUtils.isParsable(String)
isParsable() 함수는 String객체가 주어졌을때 이것을 말그대로 숫자로 변환가능한지 여부를 확인합니다.
위의 isCreatable()함수와 다르게 16진수, 8진수, 지수표기법, 끝에 'd', 'D', 'F', 'f', 'L', 'l'이 오는것들은 감지하지 않습니다.
따럿 순수 숫자로만 이루어져야 합니다.
@Test
public void apache_commons_isParsable_test() {
assertTrue(NumberUtils.isParsable("22"));
assertTrue(NumberUtils.isParsable("-23"));
assertTrue(NumberUtils.isParsable("2.2"));
assertTrue(NumberUtils.isParsable("09"));
assertFalse(NumberUtils.isParsable(null));
assertFalse(NumberUtils.isParsable(""));
assertFalse(NumberUtils.isParsable("6.2f"));
assertFalse(NumberUtils.isParsable("9.8d"));
assertFalse(NumberUtils.isParsable("22L"));
assertFalse(NumberUtils.isParsable("0xFF"));
assertFalse(NumberUtils.isParsable("2.99e+8"));
}
isCreatable() 함수와 다르게 숫자앞에 '0'이 있어도 8진법으로 인식하지 않고 10진법으로 인식합니다.
이 함수를 통해서 Integer.parseInt()같은 Java에서 제공하는 유틸함수를 대체해 숫자를 변환하거나 에러를 확인할 수 있습니다.
4.3 StringUtils.isNumeric(CharSequence)
StringUtils.isNumberic(CharSequence) 함수는 Unicode 숫자도 검사합니다.
- 유니코드로 된 모든 언어에 대해서 식별 가능합니다.
- 소수점으로 이루어진 숫자는 유니코드로 되어있지 않기에 허용하지 않습니다.
- +,-같은 양수 음수를 나타내는 기호도 허용되지 않습니다.
@Test
public void string_utils_isNumeric_test() {
assertTrue(StringUtils.isNumeric("123"));
assertTrue(StringUtils.isNumeric("١٢٣")); //아라비아 언어
assertTrue(StringUtils.isNumeric("१२३")); //데비나가리 언어
assertFalse(StringUtils.isNumeric(null));
assertFalse(StringUtils.isNumeric(""));
assertFalse(StringUtils.isNumeric(" "));
assertFalse(StringUtils.isNumeric("12 3"));
assertFalse(StringUtils.isNumeric("ab2c"));
assertFalse(StringUtils.isNumeric("12.3"));
assertFalse(StringUtils.isNumeric("-123"));
}
4.4 StringUtils.isNumericSpace(CharSequence)
StringUtils.isNumericSpace(CharSequence)는 StringUtils.isNumeric()
함수와 동일한 기능을 수행하지만 숫자 사이나 숫자 앞이나 오는 공백을 제거하여 확인합니다.
@Test
public void string_util_isNumericSpace_test() {
assertTrue(StringUtils.isNumericSpace("123"));
assertTrue(StringUtils.isNumericSpace("١٢٣"));
assertTrue(StringUtils.isNumericSpace(""));
assertTrue(StringUtils.isNumericSpace(" "));
assertTrue(StringUtils.isNumericSpace("12 3"));
assertFalse(StringUtils.isNumericSpace(null));
assertFalse(StringUtils.isNumericSpace("ab2c"));
assertFalse(StringUtils.isNumericSpace("12.3"));
assertFalse(StringUtils.isNumericSpace("-123"));
}
5. Benchmarks
위에서 살펴본 함수들을 벤치마킹 툴로 분석하여 어느때 어떤것을 사용할지 확인해보겠습니다.
참고로 본 글에서 사용된 툴은 jmh이라는 벤치마킹 툴입니다.
jmh 환경구성은 https://ms727.tistory.com/22 이 글 참고 부탁드립니다.
5.1 Simple Benchmark
첫 번재 예제로는 Integet.MAX_VALUE(0x7fffffff) 값을 가지고 이를 각각 함수에서 파싱할때 걸리는 평균 시간을 측정하고 점수를 매깁니다.
Benchmark Mode Cnt Score Error Units
CheckStringNumberBenchMarking.usingCoreJava avgt 20 6.841 ± 0.313 ns/op
CheckStringNumberBenchMarking.usingNumberUtils_isCreatable avgt 20 0.421 ± 0.348 ns/op
CheckStringNumberBenchMarking.usingNumberUtils_isParsable avgt 20 0.335 ± 0.013 ns/op
CheckStringNumberBenchMarking.usingRegularExpressions avgt 20 176.956 ± 10.107 ns/op
CheckStringNumberBenchMarking.usingStringUtils_isNumeric avgt 20 0.337 ± 0.012 ns/op
CheckStringNumberBenchMarking.usingStringUtils_isNumericSpace avgt 20 0.329 ± 0.012 ns/op
결과를 보시면 정규표현식을 사용한 방식이 가장 비용을 많이 먹었고, Apache Commons 라이브러리를 사용한 경우 대체로 동일한 성능을 보였습니다.
5.2 Enhanced Benchmark
좀 더 다양한 테스트를 진행해보겠습니다.
- 95가지의 숫자가 있습니다.(0~94, Integer.MAX_VALUE)
- 'x0', '0.005', '-11'같은 숫자 추가
- 문자열로만 이루어진 String 객체 1개
- null 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에서 제공하는 util함수를 사용했을때보다 더 나은 성능을 보이기 시작했습니다.
이 결과를 통해서 전체 테스트의 5%에 해당되는 NumberFormatException
에러가 전체 성능에 큰 영향을 미치는것을 확인할 수 있었습니다.
따라서 어떤 값을 입력하느냐에 따라 성능과 기대값은 다를 수 있다는 것도 확인할 수 있었습니다.
또한, 안전한 방법으로 Apache Commons 라이브러리처럼 보장되고 적절한 성능을 내는 외부 솔루션을 가져다 쓰는것도 괜찮다는 결론을 내릴 수 있게되었습니다.
6. 결론
이 글에서는 String 객체가 주어졌을때 이를 숫자로 변환할 수 있는 여러 방법들에 대해 설명하였습니다.
그리고 벤치마킹 툴을 사용해서 어떤 것이 적절한 성능을 내는지도 확인하였습니다.
저는 null-safe하고 빠른 속도를 내는 Apache Common Libraray를 사용하지 않을까합니다.
'Baeldung번역&공부 > Java-string' 카테고리의 다른 글
첫 문자를 대문자로 바꾸는 방법(Capitalize the First Letter of a String in Java) (0) | 2025.02.06 |
---|---|
날짜 검증하는 여러 방법(Check If a String Is a Valid Date in Java) (0) | 2025.02.05 |
문자열 분리방법(Split a String in Java) (0) | 2025.02.02 |
문자열 비어있는지 확인법(Checking for Empty or Blank Strings in Java) (0) | 2025.02.02 |
문자열에서 마지막 문자를 지우는법(How to Remove the Last Character of a String?) (0) | 2025.02.01 |