Java 9中新的货币API

更新时间:2015-01-09 09:54:01点击次数:1933次

摘要:此前,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的话,只需把下面的引用添加到工里面便能够体验下该参考实现的当前功能了:


[java] view plaincopy

<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工厂来获取:


[java] view plaincopy

// 根据货币代码来获取货币单位 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的实现必须是不可变,线程安全且可比较的。

[java] view plaincopy

/ 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的实例:


[java] view plaincopy

MonetaryAmount specAmount = MonetaryAmounts.getDefaultAmountFactory()  

                .setNumber(123.45) .setCurrency("USD") .create();  

当且仅当实现类,货币单位,以及数值全部相等时才认为这两个MontetaryAmount实例是相等的。 

[java] view plaincopy

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内包含丰富的方法,可以用来获取具体的货币,金额,精度等等: 

[java] view plaincopy

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;  

MonetaryAmount的使用


可以在MonetaryAmount上进行算术运算:


[java] view plaincopy

MonetaryAmount twelveEuro = fiveEuro.add(sevenEuro); // "EUR 12"  

MonetaryAmount twoEuro = sevenEuro.subtract(fiveEuro); // "EUR 2"  

MonetaryAmount sevenPointFiveEuro = fiveEuro.multiply(1.5); // "EUR 7.5"  

   

// MonetaryAmount can have a negative NumberValue  

MonetaryAmount minusTwoEuro = fiveEuro.subtract(sevenEuro); // "EUR -2"  

   

// some useful utility methods  

boolean greaterThan = sevenEuro.isGreaterThan(fiveEuro); // true  

boolean positive = sevenEuro.isPositive(); // true  

boolean zero = sevenEuro.isZero(); // false  

   

// Note that MonetaryAmounts need to have the same CurrencyUnit to do mathematical operations  

// this fails with: javax.money.MonetaryException: Currency mismatch: EUR/USD  

fiveEuro.add(tenUsDollar);  

舍入操作是金额换算里面非常重要的一部分。MonetaryAmount可以使用舍入操作符来进行四舍五入: 

[java] view plaincopy

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");  

MonetaryAmount dollars = Money.of(12.34567, usd);  

MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);  

MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35  

这里12.3456美金就会按当前货币默认的舍入规则来进行换算。


在操作MonetaryAmount集合时,有许多实用的工具方法可以用来进行过滤,排序以及分组。这些方法还可以与Java 8的流API一起配套使用。


看一下下面这个集合:


[java] view plaincopy

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来进行金额过滤: 

[java] view plaincopy

CurrencyUnit yen = MonetaryCurrencies.getCurrency("JPY");  

CurrencyUnit dollar = MonetaryCurrencies.getCurrency("USD");  

// 根据货币过滤,只返回美金  

// result is [USD 18, USD 7, USD 42]  

List<MonetaryAmount> onlyDollar = amounts.stream()  

    .filter(MonetaryFunctions.isCurrency(dollar))  

    .collect(Collectors.toList());  

   

// 根据货币过滤,只返回美金和日元  

// [USD 18, USD 7, JPY 13.37, USD 42]  

List<MonetaryAmount> onlyDollarAndYen = amounts.stream()  

    .filter(MonetaryFunctions.isCurrency(dollar, yen))  

    .collect(Collectors.toList());  

我们还可以过滤出大于或小于某个阈值的金额:

[java] view plaincopy

MonetaryAmount tenDollar = Money.of(10, dollar);  

   

// [USD 42, USD 18]  

List<MonetaryAmount> greaterThanTenDollar = amounts.stream()  

    .filter(MonetaryFunctions.isCurrency(dollar))  

    .filter(MonetaryFunctions.isGreaterThan(tenDollar))  

    .collect(Collectors.toList());  

排序也是类似的:

[java] view plaincopy

// Sorting dollar values by number value  

// [USD 7, USD 18, USD 42]  

List<MonetaryAmount> sortedByAmount = onlyDollar.stream()  

    .sorted(MonetaryFunctions.sortNumber())  

    .collect(Collectors.toList());  

   

// Sorting by CurrencyUnit  

// [EUR 2, JPY 13.37, USD 42, USD 7, USD 18]  

List<MonetaryAmount> sortedByCurrencyUnit = amounts.stream()  

    .sorted(MonetaryFunctions.sortCurrencyUnit())  

    .collect(Collectors.toList());  

还有分组操作:

[java] view plaincopy

// 按货币单位进行分组  

// {USD=[USD 42, USD 7, USD 18], EUR=[EUR 2], JPY=[JPY 13.37]}  

Map<CurrencyUnit, List<MonetaryAmount>> groupedByCurrency = amounts.stream()  

    .collect(MonetaryFunctions.groupByCurrencyUnit());  

   

// 分组并进行汇总  

Map<CurrencyUnit, MonetarySummaryStatistics> summary = amounts.stream()  

    .collect(MonetaryFunctions.groupBySummarizingMonetary()).get();  

   

// get summary for CurrencyUnit USD  

MonetarySummaryStatistics dollarSummary = summary.get(dollar);  

MonetaryAmount average = dollarSummary.getAverage(); // "USD 22.333333333333333333.."  

MonetaryAmount min = dollarSummary.getMin(); // "USD 7"  

MonetaryAmount max = dollarSummary.getMax(); // "USD 42"  

MonetaryAmount sum = dollarSummary.getSum(); // "USD 67"  

long count = dollarSummary.getCount(); // 3  

MonetaryFunctions还提供了归约函数,可以用来获取大值,小值,以及求和:

[java] view plaincopy

List<MonetaryAmount> amounts = new ArrayList<>();  

amounts.add(Money.of(10, "EUR"));  

amounts.add(Money.of(7.5, "EUR"));  

amounts.add(Money.of(12, "EUR"));  

   

Optional<MonetaryAmount> max = amounts.stream().reduce(MonetaryFunctions.max()); // "EUR 7.5"  

Optional<MonetaryAmount> min = amounts.stream().reduce(MonetaryFunctions.min()); // "EUR 12"  

Optional<MonetaryAmount> sum = amounts.stream().reduce(MonetaryFunctions.sum()); //  

自定义的MonetaryAmount操作


MonetaryAmount还提供了一个非常友好的扩展点叫作MonetaryOperator。MonetaryOperator是一个函数式接口,它接收一个MonetaryAmount入参并返回一个新的MonetaryAmount对象。


[java] view plaincopy

// A monetary operator that returns 10% of the input MonetaryAmount  

// Implemented using Java 8 Lambdas  

MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {  

  BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);  

  BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));  

  return Money.of(tenPercent, amount.getCurrency());  

};  

   

MonetaryAmount dollars = Money.of(12.34567, "USD");  

   

// apply tenPercentOperator to MonetaryAmount  

MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567  

标准的API特性都是通过MonetaryOperator的接口来实现的。比方说,前面看到的舍入操作就是以MonetaryOperator接口的形式来提供的。


汇率


货币兑换率可以通过ExchangeRateProvider来获取。JavaMoney自带了多个不同的ExchangeRateProvider的实现。其中重要的两个是ECBCurrentRateProvider与 IMFRateProvider。


ECBCurrentRateProvider查询的是欧洲中央银行(European Central Bank,ECB)的数据而IMFRateProvider查询的是国际货币基金组织(International Monetary Fund,IMF)的汇率。


[java] view plaincopy

// get the default ExchangeRateProvider (CompoundRateProvider)  

ExchangeRateProvider exchangeRateProvider = MonetaryConversions.getExchangeRateProvider();  

   

// get the names of the default provider chain  

// [IDENT, ECB, IMF, ECB-HIST]  

List<String> defaultProviderChain = MonetaryConversions.getDefaultProviderChain();  

   

// get a specific ExchangeRateProvider (here ECB)  

ExchangeRateProvider ecbExchangeRateProvider = MonetaryConversions.getExchangeRateProvider("ECB");  

如果没有指定ExchangeRateProvider的话返回的就是CompoundRateProvider。CompoundRateProvider会将汇率转换请求委派给一个ExchangeRateProvider链并将个返回准确结果的提供商的数据返回。

[java] view plaincopy

// get the exchange rate from euro to us dollar  

ExchangeRate rate = exchangeRateProvider.getExchangeRate("EUR", "USD");  

   

NumberValue factor = rate.getFactor(); // 1.2537 (at time writing)  

CurrencyUnit baseCurrency = rate.getBaseCurrency(); // EUR  

CurrencyUnit targetCurrency = rate.getCurrency(); // USD  

货币转换


不同货币间的转换可以通过ExchangeRateProvider返回的CurrencyConversions来完成。


[java] view plaincopy

// get the CurrencyConversion from the default provider chain  

CurrencyConversion dollarConversion = MonetaryConversions.getConversion("USD");  

   

// get the CurrencyConversion from a specific provider  

CurrencyConversion ecbDollarConversion = ecbExchangeRateProvider.getCurrencyConversion("USD");  

   

MonetaryAmount tenEuro = Money.of(10, "EUR");  

   

// convert 10 euro to us dollar   

MonetaryAmount inDollar = tenEuro.with(dollarConversion); // "USD 12.537" (at the time writing)  

请注意CurrencyConversion也实现了MonetaryOperator接口。正如其它操作一样,它也能通过MonetaryAmount.with()方法来调用。


格式化及解析


MonetaryAmount可以通过MonetaryAmountFormat来与字符串进行解析/格式化。


[java] view plaincopy

// formatting by locale specific formats  

MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMANY);  

MonetaryAmountFormat usFormat = MonetaryFormats.getAmountFormat(Locale.CANADA);  

   

MonetaryAmount amount = Money.of(12345.67, "USD");  

   

String usFormatted = usFormat.format(amount); // "USD12,345.67"  

String germanFormatted = germanFormat.format(amount); // 12.345,67 USD  

   

// A MonetaryAmountFormat can also be used to parse MonetaryAmounts from strings  

MonetaryAmount parsed = germanFormat.parse("12,4 USD");  

可以通过AmountFormatQueryBuilder来生成自定义的格式。

[java] view plaincopy

// Creating a custom MonetaryAmountFormat  

MonetaryAmountFormat customFormat = MonetaryFormats.getAmountFormat(  

    AmountFormatQueryBuilder.of(Locale.US)  

        .set(CurrencyStyle.NAME)  

        .set("pattern", "00,00,00,00.00 ¤")  

        .build());  

   

// results in "00,01,23,45.67 US Dollar"  

String formatted = customFormat.format(amount);  

注意,这里的¤符号在模式串中是作为货币的占位符。


总结


新的货币API这里已经介绍得差不多了。并且目前它的实现也已经相对稳定了(但还需要多补充些文档)。期待能在Java 9中看到这套新的接口!


上述示例可在Github中下载到。


原文出处:Michael Scharhag


译文来自:Java译站

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息