<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
判斷一個字串是否為數位是Java開發中很常見的業務需求,實現這個判斷有很多種方式,大體上分為例外處理,正規表示式,數位字元,NumberFormat工具類,外部工具類這五大類,不同型別下的實現方式其實略有不同,那麼究竟選擇哪種方式才是最好的呢?本文將一一列舉出這5類中具體8個方案,並通過豐富的測試用例來並對比這些方案的差異,相信看完本文,你將會有自己的思考。
使用例外處理的本質是基於java自身對於字串定義而實現的,如果我們的字串能夠被轉換為數位,那麼這個字串就是數位,如果轉換失敗則會丟擲異常,所以我們如果能夠捕獲異常,就可以認為它不是數位,這個方案很簡單:
public static boolean isNumeric1(String str) { try { Double.parseDouble(str); return true; } catch(Exception e){ return false; } }
如果我們的業務只要求判斷字串是否為整數,那麼只需要將Double.parseDouble(str);換成Integer.parseInt(str);即可。但是這個方案有個致命缺陷,由於判斷失敗會拋異常出來,當判斷失敗的頻率比較高,將產生較大的效能損耗。
使用正規表示式也是一種常見的判斷方式,以下的正規表示式將判斷輸入字串是否為整數或者浮點數,涵蓋負數的情況。
public static boolean isNumeric2(String str) { return str != null && str.matches("-?\d+(\.\d+)?"); }
當然,為了效能考量,這個方法最好優化成以下方式,因為上面的寫法每次呼叫時都會在matches內部間接建立一個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類的format方法將一個數值格式化為符合某個國家地區習慣的數值字串,例如我們輸入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(); }
字串的底層實現其實就是字元陣列,如果這個字元陣列中每個字元都是數位,那麼這個字串不就是數位字串了嗎?利用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流的方式,從而看起來更優雅。所以,茴香豆的‘茴’又多了一種寫法!
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時,我們通常會選擇其靜態方法isParsable和isCreatable其中的一種,它們的不同點在於isCreatable不僅可以接受十進位制數位字串,還可以接受八進位制,十六進位制以及科學計數法表示的數位字串,isParsable只接受十進位制數位字串。
public static boolean isNumeric5(String str) { return NumberUtils.isParsable(str); }
public static boolean isNumeric6(String str) { return NumberUtils.isCreatable(str); }
如果使用StringUtils,那麼要考慮到底該使用isNumeric還是isNumericSpace。二者的唯一差異在於,isNumeric會認為空字串為非法數位,isNumericSpace則認為空字串也是數位。
public static boolean isNumeric7(String str) { return StringUtils.isNumeric(str); }
public static boolean isNumeric8(String str) { return StringUtils.isNumericSpace(str); }
預設情況下,文章中的數位都指的是十進位制的阿拉伯數位(0 ,1,2 … 9),測試時會擴充套件範圍。考慮以下幾個方向:
1)null或者空字串
2)常規的數位,整數,浮點數以及負數
3)包含非法的字元,例如包含多餘的小數點,包含多餘的負號,以及其它非常規格式
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)包含非法的字元,例如包含多餘的小數點,包含多餘的負號,以及其它非常規格式 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 |
通過這個表格,可以看出不同的判斷方法,對於非常規的字串來說,差異還是比較大的。
在處理null時所有方法保持一致,這也是一個工具類該滿足的基本素養。
對於空字串來說,無論空字串長度是否大於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更加健壯,建議對其實現內部再加一層空字串的判斷,優化後的程式碼如下。
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; }
常規數位指業務中常用的數位,譬如用於表示金額的浮點數,用於統計數量的整數等。這種情況下,isNumberic1,isNumberic2,isNumberic3,isNumberic5,isNumberic6 均表現出一致性,它們判斷出來的結果都是相同的,而且也是符合我們常規預期的,是我們認為正確的結果。
對於浮點數,isNumberic4認為這不是有效數位,因為java.lang.Character#isDigit(int)認為小數點並不是數位字元。同樣的,基於StringUtils.isNumeric的 isNumberic7 和基於StringUtils.isNumericSpace的 isNumberic8 也返回了false。
如果我們檢視以上兩個方法的底層實現,就可以發現 isNumberic7,isNumberic8 和 isNumberic4 的底層實現邏輯都是一樣的,它們都是通過判斷字元是否為數位字元來實現的。以下是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 的語境中被當作十進位制對待,可以被正確解析。但是從某些業務角度或者某種特定思維上來說,數位怎麼能以0開始呢?你能接受一個以0開頭的整數或者浮點數嗎?如果你不能接受這是一個合法的數位字串,那麼很遺憾,現有的案例均不滿足需求。你似乎只能通過正規表示式來實現,重新定義你的正規表示式,來過濾掉這類不恰當的字串。
同時還需要注意,表格倒數第三行的用例是“09”,和“07”這一行類似,但isNumberic6在這兩行表現的不一致。這是由於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的內部實現換成Long.parseLong,它就可以接受 “1000L” 了呢?答案是否定的,如果我們執行以下的程式碼,系統將丟擲異常。
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是最優解。
這部分的用例就相對靈活很多了。極端情況下,比如多一個小數點,或者多一個負號,或者純粹的摻入非數位字元,isNumberic1 ~ isNumberic8均能做出有效的判斷。
但是,如果只有一個小數點,且小數點的位置不合時宜的情況下,比如“123.”,“.123”,使用例外處理的isNumberic1和使用NumberFormat的isNumberic3行為一致,均返回了true。
你可能驚呆了,這怎麼能算是有效字串呢,這種情況其實和07這個測試用例是一樣的,Java可以將它們轉換成浮點數123.0或者整數123。所以返回true對於java來說這就是合理的。如果你不滿意,那隻能考慮正則這條路了。
所有的判斷方法,均認為羅馬數位為非法數位。
使用印度數位或者阿拉伯文數位,其中 isNumberic3,isNumberic4,isNumberic5,isNumberic7,isNumberic8 能夠做出有效判斷。其它方案均無效。
如果是做國際業務的同學,你可能就要留意了,他們用本地語言填寫的電話號碼你涵蓋了嗎?
等等,那漢字表示的數位,“一”,“二”,“三”… 該用什麼方法來判斷呢? 很遺憾,本文列舉的方法均不滿足,需要自己開發相關工具類或查詢有效資料。
前面的測試用例均是十進位制數,但是一些少數場景不免會出現十進位制以外的資料。二進位制變數以 0b或0B 開始,八進位制以 0開始,十六進位制以0X或0x開始。
通過倒數第二行和倒數第三行可以看出來,只有 isNumberic6 可以準確的判斷出八進位制和十六進位制。
通過倒數第四行可以看出來,任何方法都不能判斷二進位制。
通過最後一行可以看出來,isNumberic1和isNumberic6 可以用來判斷科學計數法。
判斷一個字串是否為數位看起來是一項很簡單的業務,但是它涉及的場景卻是非常多的,從業務角度來看,沒有哪個方法是完美的。
有人說例外處理的方式不好,效能低,但是它能處理開頭和結尾為空字串的輸入,還能處理科學計數法。
有人說正則最好,但他們用的正規表示式基本都是從網上扒下來的吧,只能判斷阿拉伯數位吧,而且不能處理以0開始的字元吧。
有人說使用數位字元的方式最好,但是它無法判斷浮點數。
還有人說使用StringUtils最好,那他們有對比過NumberUtils嗎?
總之,沒有什麼方法是最好的, 最適合的才是最好的。這就和找物件一個道理,你說劉亦菲美吧,她很美,但不適合呀。
到此這篇關於Java判斷字串是否為數位多種方式的文章就介紹到這了,更多相關Java判斷字串是否為數位內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45