分享、成长,拒绝浅藏辄止。关注公号【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),我们下期见