在日常开发中经常需要用数据格式化的场景,前后端的日期格式化、金额交易的数字格式化等等。需要了都是直接网上收收,完全的拿来主义。

现在抽个时间特意来整理下 JAVA 标准类库的相关数据格式化 API。

如果你对 java 标准类库的数据格式化 API 有些了解的话你会发现 Java 对日期、对数值的格式化都有一个公用的父类 [Format](https://docs.oracle.com/javase/8/docs/api/java/text/Format.html)。除了这两个比较常用的格式化 API 之外还有 [MessageFormat](https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html)ClassicFormat,如下图所示:

Format.png

其中 **ClassicFormat**[**DateTimeFormatter**](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html) 的静态内部类,是 Java8 新增的日期格式化线程安全的类。

NumberFormat

官方 API 文档:https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html

需要说明的一点是:NumberFormat 是非线程安全的类,所以在并发场景下使用时需要注意。

NumberFormat 主要用于格式化和解析任何区域设置的数字。比如设置千分位、货币格式等。

构造实例

获取 NumberFormat 实例直接调用其静态的 getInstance() 方法即可:

  1. NumberFormat instance = NumberFormat.getInstance();

另外还有提供了几个变种实例对象:

获取正常数据格式:

  1. NumberFormat numberInstance = NumberFormat.getNumberInstance();

获取整数数据格式:

  1. NumberFormat integerInstance = NumberFormat.getIntegerInstance();

获取区域货币格式:

  1. NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();

获取百分比数据格式:

  1. NumberFormat percentInstance = NumberFormat.getPercentInstance();

除了 API 提供的标准数据格式化实例,我们还可以利用提供重载的 getInstance() 方法构造定制化实例对象:

  1. NumberFormat getInstance(Locale inLocale);
  2. NumberFormat getInstance(Locale desiredLocale, int choice);
  3. NumberFormat getInstance(LocaleProviderAdapter adapter, Locale locale, int choice)

java.util.Locale 是一个枚举类,用于指定国家和地区,比如指定国家为中国(java.util.Locale#CHINA)。

choice 指的是可选的构造实例对象,在 NumberFormat 类内部定义了如下几个常亮可供选择:

  1. // 正常数值
  2. private static final int NUMBERSTYLE = 0;
  3. // 货币
  4. private static final int CURRENCYSTYLE = 1;
  5. // 百分比
  6. private static final int PERCENTSTYLE = 2;
  7. // 科学记数
  8. private static final int SCIENTIFICSTYLE = 3;
  9. // 整形数值
  10. private static final int INTEGERSTYLE = 4;

实际上,NumberFormat 定义的几个示例对象(如货币示例)也是这几个重载的方法实现的,下面是构造货币的示例对象方法:

  1. public final static NumberFormat getCurrencyInstance() {
  2. return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE);
  3. }

常用方法

常用方法在 API 文档中都有说明,下面是部分截图:

image.png

货币格式化

比如 getCurrency() 方法,可用于得到当前区域的货币名称、货币符号等:

  1. NumberFormat instance = NumberFormat.getInstance();
  2. // 获取货币名称
  3. String displayName = instance.getCurrency().getDisplayName();
  4. System.out.println(displayName);
  5. // 获取货币符号
  6. String symbol = instance.getCurrency().getSymbol();
  7. System.out.println(symbol);

输出结果:

  1. Chinese Yuan
  2. CNY

因为程序在启动时会获取操作系统信息所以输出的货币是中国的货币 ,如果你想要输出指定国家或地区的货币符号可以在利用重载方法 getInstance(Locale inLocale) 进行实现,比如选择日本货币:

  1. NumberFormat instance = NumberFormat.getInstance(Locale.JAPAN);

现在输出结果为:

  1. Japanese Yen
  2. JPY

上面输出的都是符号,我们还可以指定格式化货币数据:

  1. // 获取货币示例
  2. NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();
  3. BigDecimal money = new BigDecimal("99999.63");
  4. System.out.println(currencyInstance.format(money));

输出示例:

  1. CNY99,999.63

如果想输出的不是货币缩写(上面的 CNY) 而是货币符号的话可以选择指定具体的区域:

  1. // 指定区域
  2. NumberFormat currencyInstance = NumberFormat.getCurrencyInstance(Locale.CHINA);
  3. BigDecimal money = new BigDecimal("99999.63");
  4. System.out.println(currencyInstance.format(money));

输出示例:

  1. 99,999.63

NumberFormat 在进行数据格式化时会默认使用千分位,即整数部分每隔三个数值会有一个英文 , 号(见上面输出示例)。如果不想使用千分位可以使用其中的 setGroupingUsed(boolean newValue) 方法:

image.png

如下:

  1. NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();
  2. // 指定不使用千分位
  3. currencyInstance.setGroupingUsed(false);
  4. BigDecimal money = new BigDecimal("99999.63");
  5. System.out.println(currencyInstance.format(money));

再次看输出结果就没有千分位了:

  1. CNY99999.63

上面的货币符号默认是元,如果想使用指定国家区域的货币同样的可以使用其重载方法:

  1. NumberFormat getCurrencyInstance(Locale inLocale);

比如使用美元作为货币:

  1. NumberFormat currencyInstance = NumberFormat.getCurrencyInstance(Locale.US);

输出结果:

  1. $99,999.63

百分比格式化

百分比格式化的使用与货币格式化基本上一致,如下示例:

  1. NumberFormat percentInstance = NumberFormat.getPercentInstance();
  2. System.out.println(percentInstance.format(0.63));

输出示例(会自动乘以 100 并加个 % 符号):

  1. 63%

数值格式化

数值格式化分为正常数值格式化和整形数值格式化,区别是整形数值格式化会舍弃小数。如下:

  1. // 正常数值格式化
  2. NumberFormat numberInstance = NumberFormat.getNumberInstance();
  3. System.out.println(numberInstance.format(10000.32));
  4. // 整形数值格式化
  5. NumberFormat integerInstance = NumberFormat.getIntegerInstance();
  6. System.out.println(integerInstance.format(10000.32));

输出示例:

  1. 10,000.32
  2. 10,000

DecimalFormat

由于 DecimalFormat 是 NumberFormat 的子类,所以功能上基本上一致。DecimalFormat 最大的特点就是可以使用 Pattern。来实现最大程度的对数据格式进行定制。

创建 DecimalFormat 实例主要有两种:调用父类的 getInstance() 方法和使用构造器。DecimalFormat 提供了三个构造方法:

  1. public DecimalFormat();
  2. public DecimalFormat(String pattern);
  3. public DecimalFormat (String pattern, DecimalFormatSymbols symbols);

设置 Pattern

DecimalFormat 设置规则主要有两种:使用构造方法和调用内部的 applyLocalizedPattern() 方法:

  1. void applyLocalizedPattern(String pattern);

Pattern 符号如下:
image.png

符号 #0 都是用于占位,区别是指定位如果没有数值 # 占位结果是空,而 0 占位没有值则使用 0 做填充。看下下面的示例:

  1. DecimalFormat format = new DecimalFormat("##.##");
  2. System.out.println(format.format(12));
  3. System.out.println(format.format(1.020));
  4. System.out.println(format.format(0.1));

输出结果为:

  1. 12
  2. 1.02
  3. 0.1

这个看起来没有什么问题,但凡是就怕比较。我们只需要简单的将 ##.## 替换为 00.00 输出结果就成了:

  1. 12.00
  2. 01.02
  3. 00.10

现在就很明显的看出了使用 #0 的区别。# 的特点是如果指定位置没有值就设置空,而 0 的话会进行使用 0 做数据填充。

设置取舍模式

取舍模式就是设置保留小数位,比如四舍五入:

  1. void setRoundingMode(RoundingMode roundingMode)

看下下面的示例:

  1. DecimalFormat format = new DecimalFormat("##.##");
  2. // 设置四舍五入模式
  3. format.setRoundingMode(RoundingMode.HALF_UP);
  4. System.out.println(format.format(12.445));

输出结果为:

  1. 12.45

设置前缀后缀

这个功能看是很有用的,比如在做金额输出时,经常需要在金额前或者后设置货币符号:

  1. // 正数前缀
  2. void setPositivePrefix (String newValue);
  3. // 正数后缀
  4. void setPositiveSuffix (String newValue);
  5. // 负数前缀
  6. void setNegativePrefix (String newValue);
  7. // 负数后缀
  8. void setNegativeSuffix (String newValue);

看下示例:

  1. DecimalFormat format = new DecimalFormat("##.##");
  2. // 设置四舍五入模式
  3. format.setRoundingMode(RoundingMode.HALF_UP);
  4. // 设置金额前缀符号
  5. format.setPositivePrefix("$");
  6. System.out.println(format.format(12.445));

输出示例:

  1. $12.45

SimpleDateFormat

SimpleDateFormat 是非线程安全的日期格式化类库。官方文档说明如下:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

该类库主要用于格式化 java.util.Date 日期 API,不能用于 Java8 新日期API(java.time) 的格式化。

总的来说,SimpleDateFormat 这个工具类一点都不新鲜,没有 Java8 之前我们使用标准类库创建日期只有一种方式:new Date(),所以不会过多介绍。


SimpleDateFormat 提供了如下几个构造方法用于创建 SimpleDateFormat 实例对象:

  1. public SimpleDateFormat();
  2. public SimpleDateFormat(String pattern);
  3. public SimpleDateFormat(String pattern, Locale locale);
  4. public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)

示例:

  1. SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
  2. simpleDateFormat.applyPattern("yyyy-MM-dd");
  3. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
  4. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd", new DateFormatSymbols());

使用 SimpleDateFormat 最主要的最大的特点就是用于设置日期 pattern。Pattern 符号如下:

image.png

并且官网文档也给了几个示例:

image.png

在使用时最多的就是用于日期和字符串之间的相互转换。

日期转字符串

将日期转换为字符串主要使用如下方法:

  1. public final String format(Date date);

示例:

  1. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  2. String format = simpleDateFormat.format(new Date());
  3. System.out.println(format);

字符串转日期

将字符串转换为日期主要使用如下方法:

  1. public Date parse(String source);

示例:

  1. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
  2. Date parse = simpleDateFormat.parse("2021-07-29");

注意:在做字符串转 Date 时一定要保证 pattern 与字符串日期格式一致!!!!

看下下面的示例:

  1. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  2. Date parse = simpleDateFormat.parse("2021-07-29");

创建 SimpleDateFormat 实例是使用的 pattern 是 yyyy-MM-dd HH:mm:ss,但是我们要格式化得日期是 2021-07-29,这个日期属于 yyyy-MM-dd pattern。

pattern 不一致在格式化时就会出现如下错误:

image.png

所以在使用时一定要注意。