JSR 354定义了一套新的Java货币API,计划会在Java 9中正式引入。本文中我们将来看一下它的参考实现:JavaMoney的当前进展。
正如我在之前那篇Java 8新的日期时间API一文中那样,本文主要也是通过一些代码来演示下新的API的用法 。
在开始之前,我想先用一段话来简短地总结一下规范定义的这套新的API的用意何在:
对许多应用而言货币价值都是一个关键的特性,但JDK对此却几乎没有任何支持。严格来讲,现有的java.util.Currency类只是代表了当前ISO 4217货币的一个数据结构,但并没有关联的值或者自定义货币。JDK对货币的运算及转换也没有内建的支持,更别说有一个能够代表货币值的标准类型了。
如果你用的是Maven的话,只需把下面的引用添加到工里面便能够体验下该参考实现的当前功能了:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>0.9</version>
</dependency>
规范中提到的类及接口都在javax.money.*包下面。
我们先从核心的两个接口CurrencyUnit与MonetaryAmount开始讲起。
CurrencyUnit及MonetaryAmount
CurrencyUnit代表的是货币。它有点类似于现在的java.util.Currency类,不同之处在于它支持自定义的实现。从规范的定义来看,java.util.Currency也是可以实现该接口的。CurrencyUnit的实例可以通过MonetaryCurrencies工厂来获取:
// 根据货币代码来获取货币单位
CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR");
CurrencyUnit usDollar = MonetaryCurrencies.getCurrency("USD");
// 根据国家及地区来获取货币单位
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MontetaryAmount代表的是某种货币的具体金额。通常它都会与某个CurrencyUnit绑定。MontetaryAmount和CurrencyUnit一样,也是一个能支持多种实现的接口。CurrencyUnit与MontetaryAmount的实现必须是不可变,线程安全且可比较的。
/ get MonetaryAmount from CurrencyUnit
CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR");
MonetaryAmount fiveEuro = Money.of(5, euro);
// get MonetaryAmount from currency code
MonetaryAmount tenUsDollar = Money.of(10, "USD");
// FastMoney is an alternative MonetaryAmount factory that focuses on performance
MonetaryAmount sevenEuro = FastMoney.of(7, euro);
Money与FastMoney是JavaMoney库中MonetaryAmount的两种实现。Money是默认实现,它使用BigDecimal来存储金额。FastMoney是可选的另一个实现,它用long类型来存储金额。根据文档来看,FastMoney上的操作要比Money的快10到15倍左右。然而,FastMoney的金额大小与精度都受限于long类型。
注意了,这里的Money和FastMoney都是具体的实现类(它们在org.javamoney.moneta.*包下面,而不是javax.money.*)。如果你不希望指定具体类型的话,可以通过MonetaryAmountFactory来生成一个MonetaryAmount的实例:
MonetaryAmount specAmount = MonetaryAmounts.getDefaultAmountFactory()
.setNumber(123.45)
.setCurrency("USD")
.create();
当且仅当实现类,货币单位,以及数值全部相等时才认为这两个MontetaryAmount实例是相等的。
MonetaryAmount oneEuro = Money.of(1, MonetaryCurrencies.getCurrency("EUR"));
boolean isEqual = oneEuro.equals(Money.of(1, "EUR")); // true
boolean isEqualFast = oneEuro.equals(FastMoney.of(1, "EUR")); // false
MonetaryAmount内包含丰富的方法,可以用来获取具体的货币,金额,精度等等:
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;