1. บทนำ
บ่อยครั้งในขณะที่ใช้งานString s เราจำเป็นต้องพิจารณาว่าStringเป็นตัวเลขที่ถูกต้องหรือไม่
ในบทช่วยสอนนี้เราจะสำรวจวิธีต่างๆในการตรวจสอบว่าสตริงที่กำหนดเป็นตัวเลขหรือไม่โดยใช้ Java ธรรมดาจากนั้นจึงใช้นิพจน์ทั่วไปและสุดท้ายโดยใช้ไลบรารีภายนอก
เมื่อเราหารือเกี่ยวกับการนำไปใช้งานต่างๆเสร็จแล้วเราจะใช้เกณฑ์มาตรฐานเพื่อให้ทราบว่าวิธีใดเหมาะสมที่สุด
2. ข้อกำหนดเบื้องต้น
เริ่มจากข้อกำหนดเบื้องต้นก่อนที่เราจะไปที่เนื้อหาหลัก
ในส่วนหลังของบทความนี้เราจะใช้ไลบรารีภายนอก Apache Commons ซึ่งเราจะเพิ่มการพึ่งพาในpom.xmlของเรา:
org.apache.commons commons-lang3 3.9
เวอร์ชันล่าสุดของไลบรารีนี้มีอยู่ใน Maven Central
3. ใช้ Plain Java
บางทีวิธีที่ง่ายและน่าเชื่อถือที่สุดในการตรวจสอบว่าStringเป็นตัวเลขหรือไม่โดยการแยกวิเคราะห์โดยใช้วิธีการในตัวของ Java:
- Integer.parseInt (สตริง)
- Float.parseFloat (สตริง)
- Double.parseDouble (สตริง)
- Long.parseLong (สตริง)
- 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แต่วิธีนี้ยังสามารถแก้ไขได้เพื่อตรวจสอบจำนวนเต็ม , ลอย , ยาวและจำนวนมากโดยใช้วิธีการแยกวิเคราะห์ใด ๆ ที่เราได้เกณฑ์ไว้ก่อนหน้านี้ .
วิธีการเหล่านี้จะกล่าวถึงในบทความ Java String Conversions
4. การใช้นิพจน์ทั่วไป
ทีนี้มาใช้ regex -? \ d + (\. \ d +)? เพื่อจับคู่สตริงตัวเลขที่ประกอบด้วยจำนวนเต็มบวกหรือลบและลอย
แต่สิ่งนี้ดำเนินไปโดยไม่ได้บอกว่าเราสามารถแก้ไขนิพจน์นี้เพื่อระบุและจัดการกับกฎต่างๆได้อย่างแน่นอน ที่นี่เราจะทำให้มันง่าย
ลองแยก regex นี้และดูว่ามันทำงานอย่างไร:
- -? - ส่วนนี้จะระบุว่าตัวเลขที่ระบุเป็นลบหรือไม่เครื่องหมายขีดกลาง " - " จะค้นหาเส้นประตามตัวอักษรและเครื่องหมายคำถาม " ? ” ทำเครื่องหมายว่ามีอยู่เป็นทางเลือกหนึ่ง
- \ d + - ค้นหาตัวเลขหนึ่งหลักขึ้นไป
- (\. \ d +)? - ส่วนนี้ของ regex คือการระบุตัวเลขลอย เรากำลังค้นหาตัวเลขหนึ่งหลักขึ้นไปตามด้วยจุด ในท้ายที่สุดเครื่องหมายคำถามแสดงว่ากลุ่มที่สมบูรณ์นี้เป็นทางเลือก
นิพจน์ทั่วไปเป็นหัวข้อที่กว้างมาก หากต้องการดูภาพรวมคร่าวๆโปรดดูบทช่วยสอนของเราเกี่ยวกับ 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
In this section, we'll discuss various methods available in the Apache Commons library.
5.1. NumberUtils.isCreatable(String)
NumberUtils from Apache Commons provides a static method NumberUtils.isCreatable(String) which checks whether a String is a valid Java number or not.
This method accepts:
- Hexadecimal numbers starting with 0x or 0X
- Octal numbers starting with a leading 0
- Scientific notation (for example 1.05e-10)
- Numbers marked with a type qualifier (for example 1L or 2.2d)
If the supplied string is null or empty/blank, then it's not considered a number and the method will return false.
Let's run some tests using this method:
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();
Note how we're getting true assertions for hexadecimal numbers, octal numbers and scientific notations in lines 6, 7 and 8 respectively.
Also, on line 14, the string “09” returns false because the preceding “0” indicates that this is an octal number and “09” is not a valid octal number.
For every input that returns true with this method, we can use NumberUtils.createNumber(String) which will give us the valid number.
5.2. NumberUtils.isParsable(String)
The NumberUtils.isParsable(String) method checks whether the given String is parsable or not.
Parsable numbers are those that are parsed successfully by any parse method like Integer.parseInt(String), Long.parseLong(String), Float.parseFloat(String) or Double.parseDouble(String).
Unlike NumberUtils.isCreatable(), this method won't accept hexadecimal numbers, scientific notations or strings ending with any type qualifier, that is, ‘f', ‘F', ‘d' ,'D' ,'l'or‘L'.
Let's look at some affirmations:
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();
On line 4, unlike NumberUtils.isCreatable(), the number starting with string “0” isn't considered as an octal number, but a normal decimal number and hence it returns true.
We can use this method as a replacement for what we did in section 3, where we’re trying to parse a number and checking for an error.
5.3. StringUtils.isNumeric(CharSequence)
The method StringUtils.isNumeric(CharSequence) checks strictly for Unicode digits. This means:
- Any digits from any language that is a Unicode digit is acceptable
- Since a decimal point is not considered as a Unicode digit, it's not valid
- Leading signs (either positive or negative) are also not acceptable
Let's now see this method in action:
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();
Note that the input parameters in lines 2 and 3 are representing numbers 123 in Arabic and Devanagari respectively. Since they're valid Unicode digits, this method returns true on them.
5.4. StringUtils.isNumericSpace(CharSequence)
The StringUtils.isNumericSpace(CharSequence) checks strictly for Unicode digits and/or space. This is same as StringUtils.isNumeric() with the only difference being that it accepts spaces as well, not only leading and trailing spaces but also if they're in between numbers:
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. Benchmarks
Before we conclude this article, let's go through some benchmark results to help us to analyze which of the above-mentioned methods are best for our use-case.
6.1. Simple Benchmark
First, we take a simple approach. We pick one string value – for our test we use Integer.MAX_VALUE. Then, that value will be tested against all our implementations:
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
As we see, the most costly operations are regular expressions. After that is our core Java-based solution.
Moreover, note that the operations using the Apache Commons library are by-and-large the same.
6.2. Enhanced Benchmark
Let's use a more diverse set of tests, for a more representative benchmark:
- 95 values are numeric (0-94 and Integer.MAX_VALUE)
- 3 contain numbers but are still malformatted — ‘x0‘, ‘0..005′, and ‘–11‘
- 1 contains only text
- 1 is a null
Upon executing the same tests, we'll see the results:
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
The most important difference is that two of our tests – the regular expressions solution and the core Java-based solution – have traded places.
จากผลลัพธ์นี้เราเรียนรู้ว่าการขว้างปาและการจัดการNumberFormatExceptionซึ่งเกิดขึ้นในกรณีเพียง 5% มีผลกระทบค่อนข้างใหญ่ต่อประสิทธิภาพโดยรวม ดังนั้นเราจึงสรุปได้ว่าทางออกที่ดีที่สุดขึ้นอยู่กับข้อมูลที่เราคาดหวัง
นอกจากนี้เราสามารถสรุปได้อย่างปลอดภัยว่าเราควรใช้วิธีการจากห้องสมุด Commons หรือวิธีการที่ใช้ในทำนองเดียวกันเพื่อประสิทธิภาพที่ดีที่สุด
7. สรุป
ในบทความนี้เราได้สำรวจวิธีต่างๆในการค้นหาว่าStringเป็นตัวเลขหรือไม่ เราดูโซลูชันทั้งสองวิธี - วิธีการในตัวและไลบรารีภายนอก
เช่นเคยการใช้งานตัวอย่างและข้อมูลโค้ดทั้งหมดที่ให้ไว้ข้างต้นรวมถึงโค้ดที่ใช้ในการวัดประสิทธิภาพสามารถพบได้บน GitHub