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;
MonetaryAmount的使用
可以在MonetaryAmount上进行算术运算:
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可以使用舍入操作符来进行四舍五入:
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一起配套使用。
看一下下面这个集合:
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");
// 根据货币过滤,只返回美金
// 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());
我们还可以过滤出大于或小于某个阈值的金额:
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());
排序也是类似的:
// 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());
还有分组操作:
// 按货币单位进行分组
// {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还提供了归约函数,可以用来获取最大值,最小值,以及求和:
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()); // "EUR 29.5"
自定义的MonetaryAmount操作
MonetaryAmount还提供了一个非常友好的扩展点叫作MonetaryOperator。MonetaryOperator是一个函数式接口,它接收一个MonetaryAmount入参并返回一个新的MonetaryAmount对象。
// 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)的汇率。
// 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链并将第一个返回准确结果的提供商的数据返回。
// 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来完成。
// 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来与字符串进行解析/格式化。
// 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来生成自定义的格式。
// 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中看到这套新的接口!
相关推荐
较为全面地总结了Java8中新时间API的基本用法(也是自我学习的总结,花点时间也是值得的),应该说面对大部分关于时间的需求,博客的内容大概也都够用了。当然由于能力和篇幅原因,博客只介绍了用法,并未去深究源码...
本书首先概述了计算机和网络安全概念并解释了Java安全模型,并在此基础上,详细描述了Java 2平台中新增加的许多安全结构方面的措施,同时对Java安全性的实施提出了使用指导,描绘了如何定制、扩展和精化安全结构以及...
本书首先概述了计算机和网络安全概念并解释了Java安全模型,并在此基础上,详细描述了Java 2平台中新增加的许多安全结构方面的措施,同时对Java安全性的实施提出了使用指导,描绘了如何定制、扩展和精化安全结构以及...
【JAVA面试题】 1. **Hashtable 和 HashMap 的区别** - **继承关系**:Hashtable 继承自 Dictionary 类,而 HashMap 是 Java 1.2 引入的 Map 接口的实现,继承自 AbstractMap 类。 - **同步性**:Hashtable 的...
**Java Development Kit (JDK) 1.7 API 文档** Java Development Kit (JDK) 是Oracle公司提供的用于开发和运行Java应用程序的核心工具集。JDK 1.7,也称为Java SE 7 (Java Standard Edition 7),是Java语言的一个...
同时,基于最新Java SE 7平台,它详细介绍了Java SE 7中新加入的lnvokeDynamic指令和方法句柄机制,给出了在Java SE 6期间引入的类型检查检验器的原理证明。本书还介绍了Java SE 5中对class文件格式的扩展变动,例如...
本书首先概述了计算机和网络安全概念并解释了Java安全模型,并在此基础上,详细描述了Java 2平台中新增加的许多安全结构方面的措施,同时对Java安全性的实施提出了使用指导,描绘了如何定制、扩展和精化安全结构以及...
本书首先概述了计算机和网络安全概念并解释了Java安全模型,并在此基础上,详细描述了Java 2平台中新增加的许多安全结构方面的措施,同时对Java安全性的实施提出了使用指导,描绘了如何定制、扩展和精化安全结构以及...
它是一个有趣的实践案例,展示了如何将Java Swing与系统API相结合,实现一个具有实际应用价值的小型工具。开发者在实现这个项目的过程中,不仅需要掌握Java语言的基本语法,还需要对操作系统的工作原理有一定的理解...
书中特别强调了Java 1.4版本中新引入的I/O和网络功能,这些新特性为本书的编写提供了进一步的理由。 #### 传统与新式Java I/O 本书首先涵盖了“传统”Java流式I/O和所谓的“新式I/O”,后者基于缓冲区和通道支持非...
Java 8 中对日期和时间API的改进是一个重要的里程碑,为开发者提供了更加高效、易用且线程安全的处理日期和时间的方式。在之前的版本中,`java.util.Date`类存在许多问题,如可变性、易出错的日期时间操作以及不易于...
本书提供了JDBCTMAPI的权威教程与参考·~JDBCAPI技术...文中有关SQL类型和Java编程语言中的类型之间的映射关系的章节、附录A、JDBC 2.0和3.0API中新特征的总结以及术语表,对所有数据库程序员来说都是必不可少的资源。
CertPath API 是 JDK 1.4 中新增加的特性,提供了证书路径验证的支持。CertPath API 允许开发者验证证书的合法性,从而确保安全通信。 Java 认证和授权服务(JAAS) Java 认证和授权服务(Java Authentication and...
本书提供了JDBCTMAPI的权威教程与参考·~JDBCAPI技术...文中有关SQL类型和Java编程语言中的类型之间的映射关系的章节、附录A、JDBC 2.0和3.0API中新特征的总结以及术语表,对所有数据库程序员来说都是必不可少的资源。
Java Web 爬虫,又称为Java Spider或Crawler,是一种自动抓取互联网信息的程序。在Java领域,实现Web爬虫技术可以帮助开发者获取大量网页数据,进行数据分析、搜索引擎优化、市场研究等多种用途。本资源"Java-Web-...
本书由Java技术的创建者编写,对Java 2平台标准版v1.2中新增加的包及类进行了全面、系统的描述,主要包括包概述和类描述。包概述是对每个包及其所有类的简要概括;而每个类描述独立成节,从类层次结构示意图、类示例...
本书提供了JDBCTMAPI的权威教程与参考·~JDBCAPI技术...文中有关SQL类型和Java编程语言中的类型之间的映射关系的章节、附录A、JDBC 2.0和3.0API中新特征的总结以及术语表,对所有数据库程序员来说都是必不可少的资源。
标题和描述中提到的"flex java环境中需要在tomcat的webapp中新加的war文件",实际上是指要将包含BlazeDS服务的WAR文件添加到Tomcat服务器的web应用程序结构中。这个WAR文件通常名为`blazeds.war`或`flex-services....