✍前言
你好,我是A哥(YourBatman)。本文所属专栏:Spring类型转换,公号后台回复专栏名即可获取全部内容。
在日常开发中,我们经常会有格式化的需求,如日期格式化、数字格式化、钱币格式化等等。
格式化器的作用似乎跟转换器的作用类似,但是它们的关注点却不一样:
- 转换器:将类型S转换为类型T,关注的是类型而非格式
- 格式化器: String <-> Java类型。这么一看它似乎和PropertyEditor类似,但是它的关注点是字符串的格式
Spring有自己的格式化器抽象org.springframework.format.Formatter,但是谈到格式化器,必然就会联想起来JDK自己的java.text.Format体系。为后文做好铺垫,本文就先介绍下JDK为我们提供了哪些格式化能力。
版本约定
JDK:8
✍正文
Java里从来都缺少不了字符串拼接的活,JDK也提供了多种“工具”供我们使用,如:StringBuffer、StringBuilder以及最直接的+号,相信这些大家都有用过。但这都不是本文的内容,本文将讲解格式化器,给你提供一个新的思路来拼接字符串,并且是推荐方案。
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); }
- format:将Object格式化为String,并将此String放到toAppendTo里面
- parseObject:讲String转换为Object,是format方法的逆向操作
Java SE针对于Format抽象类对于常见的应用场景分别提供了三个子类实现:
DateFormat:日期时间格式化
抽象类。用于用于格式化日期/时间类型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
一般来说,我们会直接使用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。同时我个人建议:在项目中可强制严令禁用
NumberFormat:数字格式化
抽象类。用于格式化数字,它可以对数字进行任意格式化,如小数、百分数、十进制数等等。它有两个实现类
类结构和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的主力了。
DecimalFormat
Decimal:小数,小数的,十进位的。
用于格式化十进制数字。它具有各种特性,可以解析和格式化数字,包括:西方数字、阿拉伯数字和印度数字。它还支持不同种类的数字,包括:整数(123)、小数(123.4)、科学记数法(1.23E4)、百分数(12%)和货币金额($123)。所有这些都可以进行本地化。
下面是它的构造器:
其中最为重要的就是这个pattern(不带参数的构造器一般不会用),它表示格式化的模式/模版。一般来说我们对DateFormat的pattern比较熟悉,但对数字格式化的模版符号了解甚少。这里我就帮你整理出这个表格(信息源自JDK官网),记得搜藏哦:
说明:Number和Digit的区别:
Number是个抽象概念,其表达形式可以是数字、手势、声音等等。如1024就是个number
Digit是用来表达的单独符号。如0-9这是个digit就可以用来表示number,如1024就是由1、0、2、4这四个digit组成的
看了这个表格的符号规则,估计很多同学还是一脸懵逼。不啰嗦了,上干货
一、0和#的使用(最常见使用场景)
这是最经典、最常见的使用场景,甚至来说你有可能职业生涯只会用到此场景。
/** * {@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
通过此案例,大致可得出如下结论:
- 整数部分:
- 0和#都可用于取出全部整数部分
- 0的个数决定整数部分长度,不够高位补0;#则无此约束,N多个#是一样的效果
- 小数部分:
- 可保留小数点后N位(0和#效果一样)
- 若小数点后位数不够,若使用的0那就低位补0,若使用#就不补(该是几位就是几位)
- 数字(1-9):并不建议模版里直接写1-9这样的数字,了解下即可
二、科学计数法E
如果你不是在证券/银行行业,这个大概率是用不着的(即使在,你估计也不会用它)。来几个例子感受一把就成:
@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