首頁 > 軟體

你知道Java判斷字串是否為數位的多種方式嗎

2022-07-06 10:01:57

前言

判斷一個字串是否為數位是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

通常使用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只接受十進位制數位字串。

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)常規的數位,整數,浮點數以及負數

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");//科學計數法
    }

由於篇幅原因,這裡就不將控制檯的列印結果貼上來了,有興趣的同學可以根據我的程式碼自己嘗試一下。

下面是將測試用例的列印結果做了個表格彙總,表頭為方法名,第一列是具體的輸入字串,表格中其它單元格表示該方法判斷是否為數位字串的結果。

用例isNumberic1isNumberic2isNumberic3isNumberic4isNumberic5isNumberic6isNumberic7isNumberic8
nullfalsefalsefalsefalsefalsefalsefalsefalse
“”falsefalsetruetruefalsefalsefalsetrue
" "falsefalsefalsefalsefalsefalsefalsetrue
“123”truetruetruetruetruetruetruetrue
“0.123”truetruetruefalsetruetruefalsefalse
“-12.3”truetruetruefalsetruetruefalsefalse
“07”truetruetruetruetruetruetruetrue
“123.”truefalsetruefalsefalsetruefalsefalse
“.123”truefalsetruefalsetruetruefalsefalse
“1.2.3”falsefalsefalsefalsefalsefalsefalsefalse
“–12.3”falsefalsefalsefalsefalsefalsefalsefalse
“-1-2.3”falsefalsefalsefalsefalsefalsefalsefalse
“-12.3-”falsefalsefalsefalsefalsefalsefalsefalse
" 123"truefalsefalsefalsefalsefalsefalsetrue
“1 23”falsefalsefalsefalsefalsefalsefalsetrue
"123 "truefalsefalsefalsefalsefalsefalsetrue
“1a2b3c”falsefalsefalsefalsefalsefalsefalsefalse
“10.0d”truefalsefalsefalsefalsetruefalsefalse
“1000L”falsefalsefalsefalsefalsetruefalsefalse
“10.0f”truefalsefalsefalsefalsetruefalsefalse
“१२३”falsefalsetruetruetruefalsetruetrue
“١٢٣”falsefalsetruetruetruefalsetruetrue
“Ⅲ”falsefalsefalsefalsefalsefalsefalsefalse
“0b100”falsefalsefalsefalsefalsefalsefalsefalse
“09”truetruetruetruetruefalsetruetrue
“0xFF”falsefalsefalsefalsefalsetruefalsefalse
“2.99e+8”truefalsefalsefalsefalsetruefalsefalse

通過這個表格,可以看出不同的判斷方法,對於非常規的字串來說,差異還是比較大的。

1)null或者空字串

在處理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;
    }

2)常規的數位,整數,浮點數以及負數

常規數位指業務中常用的數位,譬如用於表示金額的浮點數,用於統計數量的整數等。這種情況下,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是最優解。

3)包含非法的字元,例如包含多餘的小數點,包含多餘的負號,以及其它非法格式

這部分的用例就相對靈活很多了。極端情況下,比如多一個小數點,或者多一個負號,或者純粹的摻入非數位字元,isNumberic1 ~ isNumberic8均能做出有效的判斷。

但是,如果只有一個小數點,且小數點的位置不合時宜的情況下,比如“123.”,“.123”,使用例外處理的isNumberic1和使用NumberFormat的isNumberic3行為一致,均返回了true。

你可能驚呆了,這怎麼能算是有效字串呢,這種情況其實和07這個測試用例是一樣的,Java可以將它們轉換成浮點數123.0或者整數123。所以返回true對於java來說這就是合理的。如果你不滿意,那隻能考慮正則這條路了。

4)非阿拉伯數位,例如印度數位 १२३,阿拉伯文 ١٢٣,以及羅馬數位Ⅰ、Ⅱ、Ⅲ

所有的判斷方法,均認為羅馬數位為非法數位。

使用印度數位或者阿拉伯文數位,其中 isNumberic3,isNumberic4,isNumberic5,isNumberic7,isNumberic8 能夠做出有效判斷。其它方案均無效。

如果是做國際業務的同學,你可能就要留意了,他們用本地語言填寫的電話號碼你涵蓋了嗎?

等等,那漢字表示的數位,“一”,“二”,“三”… 該用什麼方法來判斷呢? 很遺憾,本文列舉的方法均不滿足,需要自己開發相關工具類或查詢有效資料。

5)非十進位制的數位,例如二進位制,八進位制,十六進位制,科學計數法表示的數位

前面的測試用例均是十進位制數,但是一些少數場景不免會出現十進位制以外的資料。二進位制變數以 0b或0B 開始,八進位制以 0開始,十六進位制以0X或0x開始。

通過倒數第二行和倒數第三行可以看出來,只有 isNumberic6 可以準確的判斷出八進位制和十六進位制。

通過倒數第四行可以看出來,任何方法都不能判斷二進位制。

通過最後一行可以看出來,isNumberic1和isNumberic6 可以用來判斷科學計數法。

小結

判斷一個字串是否為數位看起來是一項很簡單的業務,但是它涉及的場景卻是非常多的,從業務角度來看,沒有哪個方法是完美的。

有人說例外處理的方式不好,效能低,但是它能處理開頭和結尾為空字串的輸入,還能處理科學計數法。

有人說正則最好,但他們用的正規表示式基本都是從網上扒下來的吧,只能判斷阿拉伯數位吧,而且不能處理以0開始的字元吧。

有人說使用數位字元的方式最好,但是它無法判斷浮點數。

還有人說使用StringUtils最好,那他們有對比過NumberUtils嗎?

總之,沒有什麼方法是最好的, 最適合的才是最好的。這就和找物件一個道理,你說劉亦菲美吧,她很美,但不適合呀。

總結

到此這篇關於Java判斷字串是否為數位多種方式的文章就介紹到這了,更多相關Java判斷字串是否為數位內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com