你知道Java判斷字符串是否為數字的多種方式嗎
前言
判斷一個字符串是否為數字是Java開發(fā)中很常見的業(yè)務需求,實現(xiàn)這個判斷有很多種方式,大體上分為異常處理,正則表達式,數字字符,NumberFormat工具類,外部工具類這五大類,不同類型下的實現(xiàn)方式其實略有不同,那么究竟選擇哪種方式才是最好的呢?本文將一一列舉出這5類中具體8個方案,并通過豐富的測試用例來并對比這些方案的差異,相信看完本文,你將會有自己的思考。
異常處理
使用異常處理的本質是基于java自身對于字符串定義而實現(xiàn)的,如果我們的字符串能夠被轉換為數字,那么這個字符串就是數字,如果轉換失敗則會拋出異常,所以我們如果能夠捕獲異常,就可以認為它不是數字,這個方案很簡單:
public static boolean isNumeric1(String str) {
try {
Double.parseDouble(str);
return true;
} catch(Exception e){
return false;
}
}
如果我們的業(yè)務只要求判斷字符串是否為整數,那么只需要將Double.parseDouble(str);換成Integer.parseInt(str);即可。但是這個方案有個致命缺陷,由于判斷失敗會拋異常出來,當判斷失敗的頻率比較高,將產生較大的性能損耗。
正則表達式
使用正則表達式也是一種常見的判斷方式,以下的正則表達式將判斷輸入字符串是否為整數或者浮點數,涵蓋負數的情況。
public static boolean isNumeric2(String str) {
return str != null && str.matches("-?\\d+(\\.\\d+)?");
}
當然,為了性能考量,這個方法最好優(yōu)化成以下方式,因為上面的寫法每次調用時都會在matches內部間接創(chuàng)建一個Pattern實例。
private static final Pattern NUMBER_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
public static boolean isNumeric2(String str) {
return str != null && NUMBER_PATTERN.matcher(str).matches();
}
使用NumberFormat
通常使用NumberFormat類的format方法將一個數值格式化為符合某個國家地區(qū)習慣的數值字符串,例如我們輸入18,希望輸出18¥,使用這個類再好不過了,這里可以了解它的具體用法。但是也可以用該類的parse方法來判斷輸入字符串是否為數字。
public static boolean isNumeric3(String str) {
if (str == null) return false;
NumberFormat formatter = NumberFormat.getInstance();
ParsePosition pos = new ParsePosition(0);
formatter.parse(str, pos);
return str.length() == pos.getIndex();
}
數字字符
字符串的底層實現(xiàn)其實就是字符數組,如果這個字符數組中每個字符都是數字,那么這個字符串不就是數字字符串了嗎?利用java.lang.Character#isDigit(int)判斷所有字符是否為數字字符從而達到判斷數字字符串的目的:
public static boolean isNumeric4(String str) {
if (str == null) return false;
for (char c : str.toCharArray ()) {
if (!Character.isDigit(c)) return false;
}
return true;
}
如果你的java版本是8以上,以上的寫法可以替換成如下Stream流的方式,從而看起來更優(yōu)雅。所以,茴香豆的‘茴’又多了一種寫法!
public static boolean isNumeric4(String str) {
return str != null && str.chars().allMatch(Character::isDigit);
}
外部工具類
使用外部工具類通常需要引入外部jar文件,一般的依賴是apache的comons-lang:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
在使用外部工具類時,我們通常使用NumberUtils或者StringUtils,具體如下:
使用NumberUtils時,我們通常會選擇其靜態(tài)方法isParsable和isCreatable其中的一種,它們的不同點在于isCreatable不僅可以接受十進制數字字符串,還可以接受八進制,十六進制以及科學計數法表示的數字字符串,isParsable只接受十進制數字字符串。
1.NumberUtils.isParsable
public static boolean isNumeric5(String str) {
return NumberUtils.isParsable(str);
}
2.NumberUtils.isCreatable
public static boolean isNumeric6(String str) {
return NumberUtils.isCreatable(str);
}
如果使用StringUtils,那么要考慮到底該使用isNumeric還是isNumericSpace。二者的唯一差異在于,isNumeric會認為空字符串為非法數字,isNumericSpace則認為空字符串也是數字。
3.StringUtils.isNumeric
public static boolean isNumeric7(String str) {
return StringUtils.isNumeric(str);
}
4.StringUtils.isNumericSpace
public static boolean isNumeric8(String str) {
return StringUtils.isNumericSpace(str);
}
測試并比較
默認情況下,文章中的數字都指的是十進制的阿拉伯數字(0 ,1,2 … 9),測試時會擴展范圍??紤]以下幾個方向:
1)null或者空字符串
2)常規(guī)的數字,整數,浮點數以及負數
3)包含非法的字符,例如包含多余的小數點,包含多余的負號,以及其它非常規(guī)格式
4)非阿拉伯數字,例如印度數字 ???,阿拉伯文 ???,以及羅馬數字Ⅰ、Ⅱ、Ⅲ
5)非十進制的數字,例如二進制,八進制,十六進制,科學計數法表示的數字
主體測試方法如下:
public static void check(String str) {
System.out.println ( "----------checking:【" + str + "】---------" );
System.out.println(isNumeric1(str));
System.out.println(isNumeric2(str));
System.out.println(isNumeric3(str));
System.out.println(isNumeric4(str));
System.out.println(isNumeric5(str));
System.out.println(isNumeric6(str));
System.out.println(isNumeric7(str));
System.out.println(isNumeric8(str));
System.out.println( "---------------end-------------------" );
}
測試用例:
public static void main(String[] args) throws ParseException {
//1)null或者空字符串
check(null);
check("");
check(" ");
//2)正常的數字,整數或者浮點數
check("123");
check("0.123");
check("-12.3");
check("07"); //普通十進制7 or 八進制7
//3)包含非法的字符,例如包含多余的小數點,包含多余的負號,以及其它非常規(guī)格式
check("123.");
check(".123");
check("1.2.3");
check("--12.3");
check("-1-2.3");
check("-12.3-");
check(" 123");
check("1 23");
check("123 ");
check("1a2b3c");
check("10.0d"); //double類型
check("1000L"); //long類型
check("10.0f"); //float類型
//4)非阿拉伯數字,例如印度數字 ???,阿拉伯文 ???,以及羅馬數字Ⅰ、Ⅱ、Ⅲ
check("???");
check("???");
check("Ⅲ");
//5)非十進制的數字,例如二進制,八進制,十六進制,科學計數法表示的數字
check("0b100"); //二進制
check("09"); //十進制9 or 非法的八進制
check("0xFF"); //十六進制
check("2.99e+8");//科學計數法
}
由于篇幅原因,這里就不將控制臺的打印結果貼上來了,有興趣的同學可以根據我的代碼自己嘗試一下。
下面是將測試用例的打印結果做了個表格匯總,表頭為方法名,第一列是具體的輸入字符串,表格中其它單元格表示該方法判斷是否為數字字符串的結果。
| 用例 | isNumberic1 | isNumberic2 | isNumberic3 | isNumberic4 | isNumberic5 | isNumberic6 | isNumberic7 | isNumberic8 |
|---|---|---|---|---|---|---|---|---|
| null | false | false | false | false | false | false | false | false |
| “” | false | false | true | true | false | false | false | true |
| " " | false | false | false | false | false | false | false | true |
| “123” | true | true | true | true | true | true | true | true |
| “0.123” | true | true | true | false | true | true | false | false |
| “-12.3” | true | true | true | false | true | true | false | false |
| “07” | true | true | true | true | true | true | true | true |
| “123.” | true | false | true | false | false | true | false | false |
| “.123” | true | false | true | false | true | true | false | false |
| “1.2.3” | false | false | false | false | false | false | false | false |
| “–12.3” | false | false | false | false | false | false | false | false |
| “-1-2.3” | false | false | false | false | false | false | false | false |
| “-12.3-” | false | false | false | false | false | false | false | false |
| " 123" | true | false | false | false | false | false | false | true |
| “1 23” | false | false | false | false | false | false | false | true |
| "123 " | true | false | false | false | false | false | false | true |
| “1a2b3c” | false | false | false | false | false | false | false | false |
| “10.0d” | true | false | false | false | false | true | false | false |
| “1000L” | false | false | false | false | false | true | false | false |
| “10.0f” | true | false | false | false | false | true | false | false |
| “???” | false | false | true | true | true | false | true | true |
| “???” | false | false | true | true | true | false | true | true |
| “Ⅲ” | false | false | false | false | false | false | false | false |
| “0b100” | false | false | false | false | false | false | false | false |
| “09” | true | true | true | true | true | false | true | true |
| “0xFF” | false | false | false | false | false | true | false | false |
| “2.99e+8” | true | false | false | false | false | true | false | false |
通過這個表格,可以看出不同的判斷方法,對于非常規(guī)的字符串來說,差異還是比較大的。
1)null或者空字符串
在處理null時所有方法保持一致,這也是一個工具類該滿足的基本素養(yǎng)。
對于空字符串來說,無論空字符串長度是否大于0,基于StringUtils.isNumericSpace的isNumberic8均會返回true,因為它本身認為空字符串就是數字。
對于長度大于0的空字符串來說,基于NumberFormat的isNumberic3和基于java.lang.Character#isDigit(int)的isNumberic4 這兩種判斷方法都正常返回了false。
但是對于長度為0的空字符串來說,isNumberic3和isNumberic4 這兩種判斷方法出了點小插曲,它們返回了false。這是因為,對于isNumberic3來說,toCharArray或者chars方法返回長度為0的字符數組,它并沒有做一個有效的遍歷。對于isNumberic4來說,NumberFormat的起始位置和終點位置一致。
所以為了讓isNumberic3和isNumberic4更加健壯,建議對其實現(xiàn)內部再加一層空字符串的判斷,優(yōu)化后的代碼如下。
public static boolean isNumeric3(String str) {
if (str == null || str.trim ().length() == 0) return false;
NumberFormat formatter = NumberFormat.getInstance();
ParsePosition pos = new ParsePosition(0);
formatter.parse(str, pos);
return str.length() == pos.getIndex();
}
public static boolean isNumeric4(String str) {
if (str == null || str.trim ().length() == 0) return false;
for (char c : str.toCharArray ()) {
if (!Character.isDigit (c)) return false;
}
return true;
}
2)常規(guī)的數字,整數,浮點數以及負數
常規(guī)數字指業(yè)務中常用的數字,譬如用于表示金額的浮點數,用于統(tǒng)計數量的整數等。這種情況下,isNumberic1,isNumberic2,isNumberic3,isNumberic5,isNumberic6 均表現(xiàn)出一致性,它們判斷出來的結果都是相同的,而且也是符合我們常規(guī)預期的,是我們認為正確的結果。
對于浮點數,isNumberic4認為這不是有效數字,因為java.lang.Character#isDigit(int)認為小數點并不是數字字符。同樣的,基于StringUtils.isNumeric的 isNumberic7 和基于StringUtils.isNumericSpace的 isNumberic8 也返回了false。
如果我們查看以上兩個方法的底層實現(xiàn),就可以發(fā)現(xiàn) isNumberic7,isNumberic8 和 isNumberic4 的底層實現(xiàn)邏輯都是一樣的,它們都是通過判斷字符是否為數字字符來實現(xiàn)的。以下是StringUtils.isNumeric和StringUtils.isNumericSpace的源碼:
public static boolean isNumeric(CharSequence cs) {
if (isEmpty(cs)) {
return false;
} else {
int sz = cs.length();
for(int i = 0; i < sz; ++i) {
if (!Character.isDigit(cs.charAt(i))) {
return false;
}
}
return true;
}
}
public static boolean isNumericSpace(CharSequence cs) {
if (cs == null) {
return false;
} else {
int sz = cs.length();
for(int i = 0; i < sz; ++i) {
if (!Character.isDigit(cs.charAt(i)) && cs.charAt(i) != ' ') {
return false;
}
}
return true;
}
}
這里尤其注意 “07“這個字符串。在某些語境下,07是十進制的7,在另一些語境下,07是八進制的7。例如我們直接將07賦值個int變量,它確實可以通過編譯,但是把07換成09呢?一定會編譯失敗,這是因為變量聲明的場景下,07作為八進制對待,它滿足八進制的范圍要求,而八進制無法表示09,它太大了,所以編譯失敗。
但是Double.parseDouble卻可以將“09”轉化成9.0,因為這種場景下,輸入的數字作為十進制對待,0被忽略了。
int j = 07;
int k = 09; //編譯失敗,非法的八進制
System.out.println (Double.parseDouble ("09")); //打印9.0,以十進制對待
盡管以0開頭的數字字符串,在使用Double.parseDouble 的語境中被當作十進制對待,可以被正確解析。但是從某些業(yè)務角度或者某種特定思維上來說,數字怎么能以0開始呢?你能接受一個以0開頭的整數或者浮點數嗎?如果你不能接受這是一個合法的數字字符串,那么很遺憾,現(xiàn)有的案例均不滿足需求。你似乎只能通過正則表達式來實現(xiàn),重新定義你的正則表達式,來過濾掉這類不恰當的字符串。
同時還需要注意,表格倒數第三行的用例是“09”,和“07”這一行類似,但isNumberic6在這兩行表現(xiàn)的不一致。這是由于isNumberic6使用了NumberUtils.isCreatable,它把以“0”開頭的數字認為是八進制數,符合八進制范圍的返回true,不符合的返回false。所以"07"會返回true,“09”會返回false。
特別注意,當輸入為“10.0d”, “1000L”和“10.0f”時,在某種程度上也認為這是有效的數字,因為基本類型中聲明double,long和float類型的變量時,分別在字面量后面添加一個‘d’(‘D’) ,‘l’(‘L’) 和 ‘f’(‘F’)是一個很常見的操作。 這類聲明一般用來處理強制轉換,但對于這類數字字符串來說,使用 isNumberic1 的局限性就出來了,本例中基于 Double.parseDouble 來做判斷,它可以接受‘d’(‘D’) 和 ‘f’(‘F’) 結尾的數字字符串,但是不能接受以 ‘l’(‘L’) 結尾的數字字符串,以下是Double.parseDouble的部分源碼片段。
if (var6 >= var5 || var6 == var5 - 1 && (var0.charAt(var6) == 'f' || var0.charAt(var6) == 'F' || var0.charAt(var6) == 'd' || var0.charAt(var6) == 'D')) {
if (var13) {
return var1 ? A2BC_NEGATIVE_ZERO : A2BC_POSITIVE_ZERO;
}
return new FloatingDecimal.ASCIIToBinaryBuffer(var1, var3, var21, var8);
}
那是不是意味著,如果我們將isNumberic1的內部實現(xiàn)換成Long.parseLong,它就可以接受 “1000L” 了呢?答案是否定的,如果我們運行以下的代碼,系統(tǒng)將拋出異常。
System.out.println (Long.parseLong ("5562L"));
這是因為Long.parseLong的底層還是用到了Character.digit方法。以下是Long.parseLong的部分源碼片段,上述的打印將在第一個”if“塊拋出異常。
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
因此,如果你需要接受以‘d’(‘D’),‘f’(‘F’) 和 ‘l’(‘L’)結尾的數字字符串,只有isNumberic6是最優(yōu)解。
3)包含非法的字符,例如包含多余的小數點,包含多余的負號,以及其它非法格式
這部分的用例就相對靈活很多了。極端情況下,比如多一個小數點,或者多一個負號,或者純粹的摻入非數字字符,isNumberic1 ~ isNumberic8均能做出有效的判斷。
但是,如果只有一個小數點,且小數點的位置不合時宜的情況下,比如“123.”,“.123”,使用異常處理的isNumberic1和使用NumberFormat的isNumberic3行為一致,均返回了true。
你可能驚呆了,這怎么能算是有效字符串呢,這種情況其實和07這個測試用例是一樣的,Java可以將它們轉換成浮點數123.0或者整數123。所以返回true對于java來說這就是合理的。如果你不滿意,那只能考慮正則這條路了。
4)非阿拉伯數字,例如印度數字 ???,阿拉伯文 ???,以及羅馬數字Ⅰ、Ⅱ、Ⅲ
所有的判斷方法,均認為羅馬數字為非法數字。
使用印度數字或者阿拉伯文數字,其中 isNumberic3,isNumberic4,isNumberic5,isNumberic7,isNumberic8 能夠做出有效判斷。其它方案均無效。
如果是做國際業(yè)務的同學,你可能就要留意了,他們用本地語言填寫的電話號碼你涵蓋了嗎?
等等,那漢字表示的數字,“一”,“二”,“三”… 該用什么方法來判斷呢? 很遺憾,本文列舉的方法均不滿足,需要自己開發(fā)相關工具類或查找有效資料。
5)非十進制的數字,例如二進制,八進制,十六進制,科學計數法表示的數字
前面的測試用例均是十進制數,但是一些少數場景不免會出現(xiàn)十進制以外的數據。二進制變量以 0b或0B 開始,八進制以 0開始,十六進制以0X或0x開始。
通過倒數第二行和倒數第三行可以看出來,只有 isNumberic6 可以準確的判斷出八進制和十六進制。
通過倒數第四行可以看出來,任何方法都不能判斷二進制。
通過最后一行可以看出來,isNumberic1和isNumberic6 可以用來判斷科學計數法。
小結
判斷一個字符串是否為數字看起來是一項很簡單的業(yè)務,但是它涉及的場景卻是非常多的,從業(yè)務角度來看,沒有哪個方法是完美的。
有人說異常處理的方式不好,性能低,但是它能處理開頭和結尾為空字符串的輸入,還能處理科學計數法。
有人說正則最好,但他們用的正則表達式基本都是從網上扒下來的吧,只能判斷阿拉伯數字吧,而且不能處理以0開始的字符吧。
有人說使用數字字符的方式最好,但是它無法判斷浮點數。
還有人說使用StringUtils最好,那他們有對比過NumberUtils嗎?
總之,沒有什么方法是最好的, 最適合的才是最好的。這就和找對象一個道理,你說劉亦菲美吧,她很美,但不適合呀。
總結
到此這篇關于Java判斷字符串是否為數字多種方式的文章就介紹到這了,更多相關Java判斷字符串是否為數字內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot 的創(chuàng)建和運行示例代碼詳解
Spring Boot 的誕生是為了簡化Spring程序的開發(fā),今天給大家介紹下Spring Boot 的創(chuàng)建和運行,主要包括Spring Boot基本概念和springboot優(yōu)點,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2022-07-07
Java使用pulsar-flink-connector讀取pulsar catalog元數據代碼剖析
這篇文章主要介紹了Java使用pulsar-flink-connector讀取pulsar catalog元數據代碼剖析,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
Mybatis自關聯(lián)查詢一對多查詢的實現(xiàn)示例
這篇文章主要介紹了Mybatis自關聯(lián)查詢一對多查詢的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數據庫,前端框架使用bootstrap4框架,完美了實現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗吧2021-05-05
Spring Boot中l(wèi)ombok的安裝與使用詳解
這篇文章主要給大家介紹了關于Spring Boot中l(wèi)ombok安裝與使用的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-09-09

