分享、成長,拒絕淺藏輒止。關注公號【BAT的烏托邦】,回覆
專欄
獲取原創專欄:重學Spring、重學MyBatis、中間件、雲計算...本文已被 https://www.yourbatman.cn 收錄。java
你好,我是A哥(YourBatman)。本文所屬專欄:Spring類型轉換,公號後臺回覆專欄名便可獲取所有內容。git
在平常開發中,咱們常常會有格式化的需求,如日期格式化、數字格式化、錢幣格式化等等。程序員
格式化器的做用彷佛跟轉換器的做用相似,可是它們的關注點卻不同:正則表達式
<->
Java類型。這麼一看它彷佛和PropertyEditor
相似,可是它的關注點是字符串的格式Spring有本身的格式化器抽象org.springframework.format.Formatter
,可是談到格式化器,必然就會聯想起來JDK本身的java.text.Format
體系。爲後文作好鋪墊,本文就先介紹下JDK爲咱們提供了哪些格式化能力。spring
Java裏歷來都缺乏不了字符串拼接的活,JDK也提供了多種「工具」供咱們使用,如:StringBuffer、StringBuilder以及最直接的+
號,相信這些你們都有用過。但這都不是本文的內容,本文將講解格式化器,給你提供一個新的思路來拼接字符串,而且是推薦方案。sql
JDK內置有格式化器,即是java.text.Format
體系。它是個抽象類,提供了兩個抽象方法:數據庫
public abstract class Format implements Serializable, Cloneable { public abstract StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos); public abstract Object parseObject (String source, ParsePosition pos); }
Java SE針對於Format抽象類對於常見的應用場景分別提供了三個子類實現:編程
抽象類。用於用於格式化日期/時間類型java.util.Date
。雖然是抽象類,但它提供了幾個靜態方法用於獲取它的實例:數組
// 格式化日期 + 時間 public final static DateFormat getInstance() { return getDateTimeInstance(SHORT, SHORT); } public final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale){ return get(timeStyle, dateStyle, 3, aLocale); } // 格式化日期 public final static DateFormat getDateInstance(int style, Locale aLocale) { return get(0, style, 2, aLocale); } // 格式化時間 public final static DateFormat getTimeInstance(int style, Locale aLocale){ return get(style, 0, 1, aLocale); }
有了這些靜態方法,你可在沒必要關心具體實現的狀況下直接使用:安全
/** * {@link DateFormat} */ @Test public void test1() { Date curr = new Date(); // 格式化日期 + 時間 System.out.println(DateFormat.getInstance().getClass() + "-->" + DateFormat.getInstance().format(curr)); System.out.println(DateFormat.getDateTimeInstance().getClass() + "-->" + DateFormat.getDateTimeInstance().format(curr)); // 格式化日期 System.out.println(DateFormat.getDateInstance().getClass() + "-->" + DateFormat.getDateInstance().format(curr)); // 格式化時間 System.out.println(DateFormat.getTimeInstance().getClass() + "-->" + DateFormat.getTimeInstance().format(curr)); }
運行程序,輸出:
class java.text.SimpleDateFormat-->20-12-25 上午7:19 class java.text.SimpleDateFormat-->2020-12-25 7:19:30 class java.text.SimpleDateFormat-->2020-12-25 class java.text.SimpleDateFormat-->7:19:30
嗯,能夠看到底層實現實際上是我們熟悉的SimpleDateFormat
。實話說,這種作法不經常使用,狠一點:基本不會用(框架開發者可能會用作兜底實現)。
通常來講,咱們會直接使用SimpleDateFormat
來對Date進行格式化,它能夠本身指定Pattern,個性化十足。如:
@Test public void test2() { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // yyyy-MM-dd HH:mm:ss System.out.println(dateFormat.format(new Date())); }
運行程序,輸出:
2020-12-25
關於SimpleDateFormat
的使用方式再也不囉嗦,不會的就可走自行勸退手續了。此處只提醒一點:SimpleDateFormat線程不安全。
說明:JDK 8之後再也不建議使用Date類型,也就不會再使用到DateFormat。同時我我的建議:在項目中可強制嚴令禁用
抽象類。用於格式化數字,它能夠對數字進行任意格式化,如小數、百分數、十進制數等等。它有兩個實現類:
類結構和DateFormat相似,也提供了getXXXInstance
靜態方法給你直接使用,無需關心底層實現:
@Test public void test41() { double myNum = 1220.0455; System.out.println(NumberFormat.getInstance().getClass() + "-->" + NumberFormat.getInstance().format(myNum)); System.out.println(NumberFormat.getCurrencyInstance().getClass() + "-->" + NumberFormat.getCurrencyInstance().format(myNum)); System.out.println(NumberFormat.getIntegerInstance().getClass() + "-->" + NumberFormat.getIntegerInstance().format(myNum)); System.out.println(NumberFormat.getNumberInstance().getClass() + "-->" + NumberFormat.getNumberInstance().format(myNum)); System.out.println(NumberFormat.getPercentInstance().getClass() + "-->" + NumberFormat.getPercentInstance().format(myNum)); }
運行程序,輸出:
class java.text.DecimalFormat-->1,220.045 class java.text.DecimalFormat-->¥1,220.05 class java.text.DecimalFormat-->1,220 class java.text.DecimalFormat-->1,220.045 class java.text.DecimalFormat-->122,005%
這一看就知道DecimalFormat是NumberFormat的主力了。
Decimal:小數,小數的,十進位的。
用於格式化十進制數字。它具備各類特性,能夠解析和格式化數字,包括:西方數字、阿拉伯數字和印度數字。它還支持不一樣種類的數字,包括:整數(123)、小數(123.4)、科學記數法(1.23E4)、百分數(12%)和貨幣金額($123)。全部這些均可以進行本地化。
下面是它的構造器:
其中最爲重要的就是這個pattern(不帶參數的構造器通常不會用),它表示格式化的模式/模版。通常來講咱們對DateFormat的pattern比較熟悉,但對數字格式化的模版符號瞭解甚少。這裏我就幫你整理出這個表格(信息源自JDK官網),記得蒐藏哦:
符號 | Localtion | 是否本地化 | 釋義 |
---|---|---|---|
0 |
Number | 是 | Digit |
# |
Number | 是 | Digit。如果0就顯示爲空 |
. |
Number | 是 | 小數/貨幣分隔符 |
- |
Number | 是 | 就表明減號 |
, |
Number | 是 | 分組分隔符 |
E |
Number | 是 | 科學計數法分隔符(位數和指數) |
% |
前/後綴 | 是 | 乘以100並顯示爲百分數 |
¤ |
前/後綴 | 否 | 貨幣記號。若連續出現兩次就用國際貨幣符號代替 |
' |
先後綴 | 否 | 用於引用特殊字符。做用相似於轉義字符 |
說明:Number和Digit的區別:
- Number是個抽象概念,其表達形式能夠是數字、手勢、聲音等等。如1024就是個number
- Digit是用來表達的單獨符號。如0-9這是個digit就能夠用來表示number,如1024就是由一、0、二、4這四個digit組成的
看了這個表格的符號規則,估計不少同窗仍是一臉懵逼。不囉嗦了,上乾貨
這是最經典、最多見的使用場景,甚至來講你有可能職業生涯只會用到此場景。
/** * {@link DecimalFormat} */ @Test public void test4() { double myNum = 1220.0455; System.out.println("===============0的使用==============="); System.out.println("只保留整數部分:" + new DecimalFormat("0").format(myNum)); System.out.println("保留3位小數:" + new DecimalFormat("0.000").format(myNum)); System.out.println("整數部分、小數部分都5位。不夠的都用0補位(整數高位部,小數低位補):" + new DecimalFormat("00000.00000").format(myNum)); System.out.println("===============#的使用==============="); System.out.println("只保留整數部分:" + new DecimalFormat("#").format(myNum)); System.out.println("保留2爲小數並以百分比輸出:" + new DecimalFormat("#.##%").format(myNum)); // 非標準數字(不建議這麼用) System.out.println("===============非標準數字的使用==============="); System.out.println(new DecimalFormat("666").format(myNum)); System.out.println(new DecimalFormat(".6666").format(myNum)); }
運行程序,輸出:
===============0的使用=============== 只保留整數部分:1220 保留3位小數:1220.045 整數部分、小數部分都5位。不夠的都用0補位(整數高位部,小數低位補):01220.04550 ===============#的使用=============== 只保留整數部分:1220 保留2爲小數並以百分比輸出:122004.55% ===============非標準數字的使用=============== 661220 1220.666
經過此案例,大體可得出以下結論:
若是你不是在證券/銀行行業,這個大機率是用不着的(即便在,你估計也不會用它)。來幾個例子感覺一把就成:
@Test public void test5() { double myNum = 1220.0455; System.out.println(new DecimalFormat("0E0").format(myNum)); System.out.println(new DecimalFormat("0E00").format(myNum)); System.out.println(new DecimalFormat("00000E00000").format(myNum)); System.out.println(new DecimalFormat("#E0").format(myNum)); System.out.println(new DecimalFormat("#E00").format(myNum)); System.out.println(new DecimalFormat("#####E00000").format(myNum)); }
運行程序,輸出:
1E3 1E03 12200E-00001 .1E4 .1E04 1220E00000
分組分隔符比較經常使用,它就是咱們常看到的逗號,
@Test public void test6() { double myNum = 1220.0455; System.out.println(new DecimalFormat(",###").format(myNum)); System.out.println(new DecimalFormat(",##").format(myNum)); System.out.println(new DecimalFormat(",##").format(123456789)); // 分隔符,左邊是無效的 System.out.println(new DecimalFormat("###,##").format(myNum)); }
運行程序,輸出:
1,220 12,20 1,23,45,67,89 12,20
在展現層面也比較經常使用,用於把一個數字用%形式表示出來。
@Test public void test42() { double myNum = 1220.0455; System.out.println("百分位表示:" + new DecimalFormat("#.##%").format(myNum)); System.out.println("千分位表示:" + new DecimalFormat("#.##\u2030").format(myNum)); }
運行程序,輸出:
百分位表示:122004.55% 千分位表示:1220045.5‰
嗯,這個符號¤
,鍵盤竟沒法直接輸出,得使用軟鍵盤(建議使用copy大法)。
@Test public void test7() { double myNum = 1220.0455; System.out.println(new DecimalFormat(",000.00¤").format(myNum)); System.out.println(new DecimalFormat(",000.¤00").format(myNum)); System.out.println(new DecimalFormat("¤,000.00").format(myNum)); System.out.println(new DecimalFormat("¤,000.¤00").format(myNum)); // 世界貨幣表達形式 System.out.println(new DecimalFormat(",000.00¤¤").format(myNum)); }
運行程序,輸出:
1,220.05¥ 1,220.05¥ ¥1,220.05 1,220.05¥¥ ¥1,220.05¥ 1,220.05CNY
注意最後一條結果:若是連續出現兩次,表明貨幣符號的國際代號。
說明:結果默認都作了Locale本地化處理的,若你在其它國家就不會再是¥人名幣符號嘍
DecimalFormat就先介紹到這了,其實掌握了它就基本等於掌握了NumberFormat。接下來再簡要看看它另一個「兒子」:ChoiceFormat。
Choice:精選的,仔細推敲的。
這個格式化器很是有意思:至關於以數字爲鍵,字符串爲值的鍵值對。使用一組double類型的數組做爲鍵,一組String類型的數組做爲值,兩數組相同(不必定必須是相同,見示例)索引值的元素做爲一對。
@Test public void test8() { double[] limits = {1, 2, 3, 4, 5, 6, 7}; String[] formats = {"週一", "週二", "週三", "週四", "週五", "週六", "周天"}; NumberFormat numberFormat = new ChoiceFormat(limits, formats); System.out.println(numberFormat.format(1)); System.out.println(numberFormat.format(4.3)); System.out.println(numberFormat.format(5.8)); System.out.println(numberFormat.format(9.1)); System.out.println(numberFormat.format(11)); }
運行程序,輸出:
週一 週四 週五 周天 周天
結果解釋:
可能你會想這有什麼使用場景???是的,不得不認可它的使用場景較少,本文下面會介紹下它和MessageFormat
的一個使用場景。
若是說DateFormat
和NumberFormat
都用沒什麼花樣,主要記住它的pattern語法格式就成,那麼就下來這個格式化器就是本文的主菜了,使用場景很是的普遍,它就是MessageFormat
。
MessageFormat提供了一種與語言無關(無論你在中國仍是其它國家,效果同樣)的方式生成拼接消息/拼接字符串的方法。使用它來構造顯示給最終用戶的消息。MessageFormat接受一組對象,對它們進行格式化,而後在模式的適當位置插入格式化的字符串。
先來個最簡單的使用示例體驗一把:
/** * {@link MessageFormat} */ @Test public void test9() { String sourceStrPattern = "Hello {0},my name is {1}"; Object[] args = new Object[]{"girl", "YourBatman"}; String formatedStr = MessageFormat.format(sourceStrPattern, args); System.out.println(formatedStr); }
運行程序,輸出:
Hello girl,my name is YourBatman
有沒有中似曾類似的感受,是否是和String.format()
的做用特別像?是的,它倆的用法區別,到底使用稅文下也會討論。
要熟悉MessageFormat的使用,主要是要熟悉它的參數模式(你也能夠理解爲pattern)。
MessageFormat採用{}
來標記須要被替換/插入的部分,其中{}
裏面的參數結構具備必定模式:
ArgumentIndex[,FormatType[,FormatStyle]]
ArgumentIndex
:非必須。從0
開始的索引值FormatType
:非必須。使用不一樣的java.text.Format
實現類對入參進行格式化處理。它能有以下值:
FormatStyle
:非必須。設置FormatType使用的樣式。它能有以下值:
說明:FormatType和FormatStyle只有在傳入值爲日期時間、數字、百分比等類型時纔有可能須要設置,使用得並很少。畢竟:我在外部格式化好後再放進去不香嗎?
@Test public void test10() { MessageFormat messageFormat = new MessageFormat("Hello, my name is {0}. I’am {1,number,#.##} years old. Today is {2,date,yyyy-MM-dd HH:mm:ss}"); // 亦可經過編程式 顯示指定某個位置要使用的格式化器 // messageFormat.setFormatByArgumentIndex(1, new DecimalFormat("#.###")); System.out.println(messageFormat.format(new Object[]{"YourBatman", 24.123456, new Date()})); }
運行程序,輸出:
Hello, my name is YourBatman. I’am 24.12 years old. Today is 2020-12-26 15:24:28
它既能夠直接在模版裏指定格式化模式類型,也能夠經過API方法set指定格式化器,固然你也能夠再外部格式化好後再放進去,三種方式都可,任君選擇。
下面基於此示例,對MessageFormat的使用注意事項做出幾點強調。
@Test public void test11() { System.out.println(MessageFormat.format("{1} - {1}", new Object[]{1})); // {1} - {1} System.out.println(MessageFormat.format("{0} - {1}", new Object[]{1})); // 輸出:1 - {1} System.out.println(MessageFormat.format("{0} - {1}", new Object[]{1, 2, 3})); // 輸出:1 - 2 System.out.println("---------------------------------"); System.out.println(MessageFormat.format("'{0} - {1}", new Object[]{1, 2})); // 輸出:{0} - {1} System.out.println(MessageFormat.format("''{0} - {1}", new Object[]{1, 2})); // 輸出:'1 - 2 System.out.println(MessageFormat.format("'{0}' - {1}", new Object[]{1, 2})); // {0} - 2 // 若你數據庫值兩邊都須要''包起來,請你這麼寫 System.out.println(MessageFormat.format("''{0}'' - {1}", new Object[]{1, 2})); // '1' - 2 System.out.println("---------------------------------"); System.out.println(MessageFormat.format("0} - {1}", new Object[]{1, 2})); // 0} - 2 System.out.println(MessageFormat.format("{0 - {1}", new Object[]{1, 2})); // java.lang.IllegalArgumentException: Unmatched braces in the pattern. }
''
纔算做一個'
,若只寫一個將被忽略甚至影響整個表達式
'
'
的匹配關係{}
只寫左邊報錯,只寫右邊正常輸出(注意參數的對應關係)咱們知道MessageFormat
提供有一個static靜態方法,很是方便的的使用:
public static String format(String pattern, Object ... arguments) { MessageFormat temp = new MessageFormat(pattern); return temp.format(arguments); }
能夠清晰看到,該靜態方法本質上仍是構造了一個MessageFormat
實例去作格式化的。所以:若你要屢次(如高併發場景)格式化同一個模版(參數可不同)的話,那麼提早建立好一個全局的(非static) MessageFormat實例再執行格式化是最好的,而非一直調用其靜態方法。
說明:若你的系統非高併發場景,此性能損耗基本無需考慮哈,怎麼方便怎麼來。畢竟朝生夕死的對象對JVM來講沒啥壓力
兩者都能用於字符串拼接(格式化)上,撇開MessageFormat支持各類模式不說,咱們只須要考慮它倆的性能上差別。
{}
數組並記錄位置"%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"
一說到正則表達式,我內心就發觸,由於它對性能是不友好的,因此孰優孰劣,高下立判。
說明:仍是那句話,沒有絕對的誰好誰壞,若是你的系統對性能不敏感,那就是方便第一
這個就不少啦,最多見的有:HTML拼接、SQL拼接、異常信息拼接等等。
好比下面這個SQL拼接:
StringBuilder sb =new StringBuilder(); sb.append("insert into user ("); sb.append(" name,"); sb.append(" accountId,"); sb.append(" zhName,"); sb.append(" enname,"); sb.append(" status"); sb.append(") values ("); sb.append(" ''{0}'',"); sb.append(" {1},"); sb.append(" ''{2}'',"); sb.append(" ''{3}'',"); sb.append(" {4},"); sb.append(")"); Object[] args = {name, accountId, zhName, enname, status}; // 最終SQL String sql = MessageFormat.format(sb.toString(), arr);
你看,多工整。
說明:若是值是字符串須要
'
包起來,那麼請使用兩邊各兩個包起來
本文內容介紹了JDK原生的格式化器知識點,主要做用在這三個方面:
Spring是直接面向使用者的框架產品,很顯然這些是不夠用的,而且JDK的格式化器在設計上存在一些弊端。好比常常被吐槽的:日期/時間類型格式化器SimpleDateFormat
爲毛在java.text包裏,而它格式化的類型Date卻在java.util包內,這實爲不合適。
有了JDK格式化器做爲基礎,下篇咱們就能夠浩浩蕩蕩的走進Spring格式化器的大門了,看看它是如何優於JDK進行設計和抽象的。
【Spring類型轉換】系列:
【Jackson】系列:
【數據校驗Bean Validation】系列:
【新特性】系列:
【程序人生】系列:
還有諸如【Spring配置類】【Spring-static關鍵字】【Spring數據綁定】【Spring Cloud Netflix】【Feign】【Ribbon】【Hystrix】...更多原創專欄,關注BAT的烏托邦
回覆專欄
二字便可所有獲取,也可加我fsx1056342982
,交個朋友。
有些已完結,有些連載中。我是A哥(YourBatman),我們下期見