JAVA NumberFormat和DecimalFormat小结
中文互聯網上很多介紹這兩個類的博客質量真是一眼難盡,遇到什么問題想百度的時候發現就是屎里淘金,非常浪費時間。格式化數字這種不常用但是一定有機會遇到的場景,還是提前做好功課為好。
本篇文章簡單說明一下NumberFormat和DecimalFormat這兩個類,主要是我對這兩個類用法的一些理解。
首先是類的繼承關系:
可以看到在JAVA的Format家族中,主要分為3個分支,分別是
- 格式化日期時間的DateFormat分支,主要用的其實現類SimpleDateFormat
- 格式化文本消息的MessageFormat分支,自己就是實現類,常和它的親戚ChoiceFormat配合使用
- 格式化數字的NumberFormat分支,主要用的是NumberFormat和DecimalFormat
在上面的界面右鍵-Show Categories-選擇Methods,再右鍵-Change Visiable Level-選擇Public,可以看到類中所有的public方法
可以看到里面的方法相當的多,但是由于我們使用Format類家族的時候,目的都是為了將對象轉化為對應的字符串表示形式,或者反過來將字符串轉化為對應的對象。聽起來有點像序列化和反序列化,不過實際還是差別很大的(序列化是將對象轉變成二進制的字節數組,以保存成文件或者通過網絡傳輸,序列化后的文件人類是看不懂的。而format則是將對象變成它的字符串表現形式,這個字符串就是專門設計為讓人類可以看懂的)
所以對于Format類家族中的方法,我們主要只需要關心在抽象父類Format中定義的兩組重載方法:
- format()
- parseObject()
顧名思義,format中文意思是格式化,就是是將某個對象格式化為字符串;parse中文意思是解析,就是將字符串轉化為對象。理論上來說,將一個對象a先用format格式化為字符串,再將字符串解析為對象b,對象a和b應該是相等的。讀者可以實驗一下,下面的代碼運行結果是true。
public void test() throws ParseException {Format format = new DecimalFormat();Object obj = 12.3;Object newObj = format.parseObject(format.format(obj));System.out.println(newObj.equals(obj)); }當然,由于parseObject()方法的返回值類型是Object,為了使用上的方便,各個子類都有其對應的parse()方法以返回更具體的對象,比如NumberFormat:
再比如DateFormat:
在format()和parse()兩組方法之中,其實以format()方法的使用場景占多數。因為本文討論的是NumberFormat和DecimalFormat,所以下文的結論只針對數字的格式化。
首先,我們要理解在JAVA中,NumberFormat類和DecimalFormat類究竟是用來干什么的。這一點在源碼的注釋里已經有答案了。
NumberFormat helps you to format and parse numbers for any locale. Your code can be completely independent of the locale conventions for decimal points, thousands-separators, or even the particular decimal digits used, or whether the number format is even decimal.
翻譯:NumberFormat幫助您格式化和解析任何地區的數字。您的代碼可以完全獨立于小數點、千位分隔符、甚至所使用的特定小數位數的語言環境約定,或者數字格式是否為十進制。
DecimalFormat is a concrete subclass of NumberFormat that formats decimal numbers. It has a variety of features designed to make it possible to parse and format numbers in any locale, including support for Western, Arabic, and Indic digits. It also supports different kinds of numbers, including integers (123), fixed-point numbers (123.4), scientific notation (1.23E4), percentages (12%), and currency amounts ($123). All of these can be localized.
翻譯:DecimalFormat是NumberFormat的一個具體子類,用于格式化十進制數字。它具有各種設計用來解析和格式化任何語言環境中的數字的特性,包括對西方數字、阿拉伯數字和印度數字的支持。它還支持不同種類的數字,包括整數(123)、定點數字(123.4)、科學記數法(1.23E4)、百分比(12%)和貨幣金額($123)。所有這些都可以本地化。
也就是說,NumberFormat和DecimalFormat是被設計用來格式化所有地區對應數字格式的全能類。簡單解釋一下:在JAVA中,數字只有那幾種表示數字的基本類型和它們對應的包裝類(int,Integer,long,Long等),但是這些對象只能存在于JAVA虛擬機中,要讓人類看見這些數字,就需要將它們轉化成字符串。在不同的地區,表示數字的習慣千差萬別,如果我們的代碼要對不同地區的人們輸出以不同方式表示的數字,就輪到NumberFormat和DecimalFormat出場了,它們之間的區別就在于前者是格式化數字,而后者是格式化十進制數字。
為了讓使用者理解不同地區的數字表示方式究竟“有何不同”,在DecimalFormat的類注釋中甚至給出了下面一段示例代碼:
輸出的結果很長,我就不全部貼上來了,讀者可以自行測試。我只拿其中幾條比較有特點的數據來展示。以下是case 0:分支的輸出結果,在該分支中座的操作是將-1234.56格式化為小數形式的字符串,再將字符串解析回數字:
阿拉伯文 (阿拉伯聯合酋長國): #,##0.###;#,##0.###- -> 1,234.56- -> -1234.56
中文 (中國): #,##0.### -> -1,234.56 -> -1234.56
芬蘭文 (芬蘭): #,##0.### -> -1 234,56 -> -1234.56
波蘭文 (波蘭): #,##0.### -> -1 234,56 -> -1234.56
法文 (瑞士): #,##0.### -> -1’234.56 -> -1234.56
法文 (盧森堡): #,##0.### -> -1 234,56 -> -1234.56
法文 (比利時): #,##0.### -> -1.234,56 -> -1234.56
西班牙文 (委內瑞拉): #,##0.### -> -1.234,56 -> -1234.56
泰文 (泰國,TH): #,##0.### -> -?,???.?? -> -1234.56
印地文 (印度): #,##0.### -> -?,???.?? -> -1234.56
可以看到,在所有的輸出中代碼都成功地將-1234.56"format"成了對應的字符串表示,然后又"pase"回了-1234.56,但是在不同的國家和地區,數字的字符串表示方式卻差異極大:
中國是我們熟悉的用英文逗號做千分位符,英文句號做小數點,但是芬蘭和波蘭就是用空格作千分位符,瑞內瑞拉則是用英文句號做千分位符,英文逗號做小數點,跟中國正好相反;
同樣使用法文的三個國家——瑞士、盧森堡、比利時——它們對同一個數字的表示方式還全都不一樣;
更奇葩的是泰國和印度,在泰文和印地文中,甚至不用阿拉伯數字來表示數字;
而在阿拉伯數字的起源地,使用阿拉伯文的阿聯酋,他們寫負數時,是把負號放在數字后邊的……
看了我摘選出來的幾個例子,我想讀者應該能理解在表現數字的方式上,“世界的參差”了吧。而這,只是case 0:分支,將-1234.56格式化為小數后的字符串表示。還有三個分支分別是轉成整數、貨幣、百分數,我就不貼上來了,有興趣的自己復制代碼執行看一下吧。
可以想象,如果JAVA不給我們提供NumberFormat類和DecimalFormat類,要我們自己實現“針對不同地區的用戶,提供對應的個性化的表示數字的字符串輸出”這一功能會有多復雜了。
很可惜的一件事是,雖然我前面寫了這么多,說明了Format類“地區化輸出”以及將“地區化輸出”的字符串無損解析成JAVA數字對象的功能有多么強大,但是對于絕大多數開發者來說,其實是很少用到“地區化輸出”這一功能的。相反,在我們的日常工作中,實際的需求可能是“保留xx位小數”、“百分數和小數的相互轉化”、“將數字轉化成包含萬分位符的格式”等等。所以盡管NumberFormat可以在無其它配置的情況下將-1234.56正確地格式化為" -1,234.56"字符串,由于默認格式無法滿足需求,我們往往需要自定義格式化模板,就是在DecimalFormat的構造方法中傳入一個符合預設語法標準的字符串,告訴DecimalFormat要如何格式化數字。
以下是我總結的NumberFormat和DecimalFormat的format()方法自定義模板時的注意要點:
1、Format對象的聲明類型什么時候用NumberFormat,什么時候用DecimalFormat
2、如何實例化NumberFormat和DecimalFormat對象
目前Format對象的獲取有2種方式:
實際上,由于所有NumberFormat.getInstance()方法拿到的NumberFormat實例的實際類型都是DecimalFormat,所以兩種獲取實例方式的區別僅在于可自定義程度的大小。使用方式1獲取實例,由于聲明類型是NumberFormat,所以只能通過幾個set方法自定義幾個有限的參數,比如整數與小數部分的最大和最小位數、舍入方式、貨幣類型、是否展示千分位符等;使用方式2獲取實例,除了可以自定義上述參數外,還可以實現很多其它的自定義模板,比如科學計數法、千分數、使用萬分位符等等。
所以,對于上面兩個問題,我的建議是:如果格式化的是貨幣、百分數等沒有太大自定義需求的字符串,可以使用方式1獲取聲明類型為NumberFormat的實例,否則,就用方式2,獲取DecimalFormat實例。
3、模板字符串中幾個常用符號的說明
下表在DecimalFormat類的注釋里有,我只是對每個符號的含義添加了中文說明
| 0 | Number | Yes | Digit | 數字 |
| # | Number | Yes | Digit, zero shows as absent | 數字,如果是0則不展示 |
| . | Number | Yes | Decimal separator or monetary decimal separator | 數字或者貨幣的小數位分隔符 |
| - | Number | Yes | Minus sign | 負號 |
| , | Number | Yes | Grouping separator | 英文逗號。分組分隔符(千分位符、萬分位符) |
| E | Number | Yes | Separates mantissa and exponent in scientific notation. Need not be quoted in prefix or suffix. | 科學計數法中用來分離尾數和指數的符號。不能用在前綴或者后綴中。(科學計數法中a·10n10^n10n記作aEn,其中a叫做底數,n叫做指數) |
| ; | Subpattern boundary | Yes | Separates positive and negative subpatterns | 分隔正數和負數子模式 |
| % | Prefix or suffix | Yes | Multiply by 100 and show as percentage | 乘以100并以百分數顯示 |
| \u2030 | Prefix or suffix | Yes | Multiply by 1000 and show as per mille value | 乘以1000并以千分數顯示 |
| ¤ (\u00A4) | Prefix or suffix | No | Currency sign, replaced by currency symbol. If doubled, replaced by international currency symbol. If present in a pattern, the monetary decimal separator is used instead of the decimal separator. | 貨幣記號,由貨幣符號替換。如果兩個同時出現,則用國際貨幣符號替換。如果出現在某個模式中,則使用貨幣小數分隔符,而不使用小數分隔符 |
| ’ | Prefix or suffix | No | Used to quote special characters in a prefix or suffix, for example, “’#’#” formats 123 to “#123”. To create a single quote itself, use two in a row: “# o’'clock”. | 英文單引號。在特殊字符左右用單引號圍起來可以用于標識特殊字符,比如使用模式"’#’#“可以將123格式化為”#123"。如果想要表示單引號本身,則使用兩個單引號,如"# o’'clock" |
3.1、符號0和#的區別
兩者是自定義模板時最常用到的符號,區別在于,在數字前后遇到0時,如果用"0"會強制顯示,如果用"#"則會省略。
// 需求:將精度為6位小數的數字截取為保留4位小數 DecimalFormat df1 = new DecimalFormat("#.0000"); DecimalFormat df2 = new DecimalFormat("#.####"); double d1 = 12.345678; double d2 = 12.340000; System.out.println("使用\"#.0000\"模板得到的結果:"); System.out.println(df1.format(d1)); System.out.println(df1.format(d2)); System.out.println("使用\"#.####\"模板得到的結果:"); System.out.println(df2.format(d1)); System.out.println(df2.format(d2));輸出結果如下:
使用"#.0000"模板得到的結果:
12.3457
12.3400
使用"#.####"模板得到的結果:
12.3457
12.34
3.2、負號"-“與子模式分隔符”;"的使用
一般來說,這兩個符號是組合使用的。在默認情況下,DecimalFormat在格式化負數時,會自動在前面加上一個符號"-",但是如果你想自定義負號的位置(就如前面官方例子中的阿聯酋一樣),就需要再寫一個負數子模式,放在正數子模式后面,中間用";"分隔。
double d1 = 123.4567; double d2 = -123.4567; DecimalFormat df1 = new DecimalFormat("#.00"); DecimalFormat df2 = new DecimalFormat("#.00;#.00-"); System.out.println("使用\"#.00\"模板得到的結果:"); System.out.println(df1.format(d1)); System.out.println(df1.format(d2)); System.out.println("使用\"#.00;#.00-\"模板得到的結果:"); System.out.println(df2.format(d1)); System.out.println(df2.format(d2));輸出結果如下:
使用"#.00"模板得到的結果:
123.46
-123.46
使用"#.00;#.00-"模板得到的結果:
123.46
123.46-
3.3、百分數符號"%" 與千分數符號"\u2030"
由于千分符號"‰"不方便在普通鍵盤上打出,所以DecimalFormat的設計者使用它的Unicode編碼來代替。在使用上,百分數符號和千分數符號沒有什么不同
double d1 = 12.34567; double d2 = -12.34567; DecimalFormat df1 = new DecimalFormat("0.00%"); System.out.println("使用\"0.00%\"模板得到的結果:"); System.out.println(df1.format(d1)); System.out.println(df1.format(d2)); DecimalFormat df2 = new DecimalFormat("0.00\u2030"); System.out.println("使用\"0.00\u2030\"模板得到的結果:"); System.out.println(df2.format(d1)); System.out.println(df2.format(d2));輸出結果如下:
使用"0.00%"模板得到的結果:
1234.57%
-1234.57%
使用"0.00‰"模板得到的結果:
12345.67‰
-12345.67‰
3.4、自定義千分位符、萬分位符
由于中國在讀數字時習慣以萬為單位分隔大數,所以將數字以萬分隔是很常見的需求
double d = 123456789.87654; DecimalFormat df1 = new DecimalFormat("#,####.#"); DecimalFormat df2 = new DecimalFormat("#,###.#"); System.out.println("使用\"#,####.#\"模板得到的結果:"); System.out.println(df1.format(d)); System.out.println("使用\"#,###.#\"模板得到的結果:"); System.out.println(df2.format(d));輸出結果如下:
使用"#,####.#“模板得到的結果:
1,2345,6789.9
使用”#,###.#"模板得到的結果:
123,456,789.9
總結
以上是生活随笔為你收集整理的JAVA NumberFormat和DecimalFormat小结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机:导致手机发烫的原因有哪些?
- 下一篇: mysql 静态表 是不是 myisam