此前,Oracle公布Java 9首个增强计划集(众所周知的JEPs)确定会在2016年早些时候发布。而目前,JSR 354定义了一套新的Java货币API,计划会在Java 9中正式引入。
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 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);
-
-
- MonetaryAmount tenUsDollar = Money.of(10, "USD");
-
-
- 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"));
- boolean isEqualFast = oneEuro.equals(FastMoney.of(1, "EUR"));
MonetaryAmount内包含丰富的方法,可以用来获取具体的货币,金额,精度等等:
- MonetaryAmount monetaryAmount = Money.of(123.45, euro);
- CurrencyUnit currency = monetaryAmount.getCurrency();
- NumberValue numberValue = monetaryAmount.getNumber();
-
- int intValue = numberValue.intValue();
- double doubleValue = numberValue.doubleValue();
- long fractionDenominator = numberValue.getAmountFractionDenominator();
- long fractionNumerator = numberValue.getAmountFractionNumerator();
- int precision = numberValue.getPrecision();
-
-
-
- Number number = numberValue;
MonetaryAmount的使用
可以在MonetaryAmount上进行算术运算:
- MonetaryAmount twelveEuro = fiveEuro.add(sevenEuro);
- MonetaryAmount twoEuro = sevenEuro.subtract(fiveEuro);
- MonetaryAmount sevenPointFiveEuro = fiveEuro.multiply(1.5);
-
-
- MonetaryAmount minusTwoEuro = fiveEuro.subtract(sevenEuro);
-
-
- boolean greaterThan = sevenEuro.isGreaterThan(fiveEuro);
- boolean positive = sevenEuro.isPositive();
- boolean zero = sevenEuro.isZero();
-
-
-
- fiveEuro.add(tenUsDollar);
舍入操作是金额换算里面非常重要的一部分。MonetaryAmount可以使用舍入操作符来进行四舍五入:
- CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
- MonetaryAmount dollars = Money.of(12.34567, usd);
- MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
- MonetaryAmount roundedDollars = dollars.with(roundingOperator);
这里12.3456美金就会按当前货币默认的舍入规则来进行换算。
在操作MonetaryAmount集合时,有许多实用的工具方法可以用来进行过滤,排序以及分组。这些方法还可以与Java 8的流API一起配套使用。
看一下下面这个集合:
- List<MonetaryAmount> amounts = new ArrayList<>();
- amounts.add(Money.of(2, "EUR"));
- amounts.add(Money.of(42, "USD"));
- amounts.add(Money.of(7, "USD"));
- amounts.add(Money.of(13.37, "JPY"));
- amounts.add(Money.of(18, "USD"));
我们可以根据CurrencyUnit来进行金额过滤:
- CurrencyUnit yen = MonetaryCurrencies.getCurrency("JPY");
- CurrencyUnit dollar = MonetaryCurrencies.getCurrency("USD");
-
-
- List<MonetaryAmount> onlyDollar = amounts.stream()
- .filter(MonetaryFunctions.isCurrency(dollar))
- .collect(Collectors.toList());
-
-
-
- List<MonetaryAmount> onlyDollarAndYen = amounts.stream()
- .filter(MonetaryFunctions.isCurrency(dollar, yen))
- .collect(Collectors.toList());
我们还可以过滤出大于或小于某个阈值的金额:
- MonetaryAmount tenDollar = Money.of(10, dollar);
-
-
- List<MonetaryAmount> greaterThanTenDollar = amounts.stream()
- .filter(MonetaryFunctions.isCurrency(dollar))
- .filter(MonetaryFunctions.isGreaterThan(tenDollar))
- .collect(Collectors.toList());
排序也是类似的:
-
-
- List<MonetaryAmount> sortedByAmount = onlyDollar.stream()
- .sorted(MonetaryFunctions.sortNumber())
- .collect(Collectors.toList());
-
-
-
- List<MonetaryAmount> sortedByCurrencyUnit = amounts.stream()
- .sorted(MonetaryFunctions.sortCurrencyUnit())
- .collect(Collectors.toList());
还有分组操作:
-
-
- Map<CurrencyUnit, List<MonetaryAmount>> groupedByCurrency = amounts.stream()
- .collect(MonetaryFunctions.groupByCurrencyUnit());
-
-
- Map<CurrencyUnit, MonetarySummaryStatistics> summary = amounts.stream()
- .collect(MonetaryFunctions.groupBySummarizingMonetary()).get();
-
-
- MonetarySummaryStatistics dollarSummary = summary.get(dollar);
- MonetaryAmount average = dollarSummary.getAverage();
- MonetaryAmount min = dollarSummary.getMin();
- MonetaryAmount max = dollarSummary.getMax();
- MonetaryAmount sum = dollarSummary.getSum();
- long count = dollarSummary.getCount();