Java 9中新的货币API
译文出处: Java译站 原文出处:Michael Scharhag
JSR 354定义了一套新的Java货币API,计划会在Java 9中正式引入。本文中我们将来看一下它的参考实现:JavaMoney的当前进展。
正如我在之前那篇Java 8新的日期时间API一文中那样,本文主要也是通过一些代码来演示下新的API的用法 。
在开始之前,我想先用一段话来简短地总结一下规范定义的这套新的API的用意何在:
对许多应用而言货币价值都是一个关键的特性,但JDK对此却几乎没有任何支持。严格来讲,现有的java.util.Currency类只是代表了当前ISO 4217货币的一个数据结构,但并没有关联的值或者自定义货币。JDK对货币的运算及转换也没有内建的支持,更别说有一个能够代表货币值的标准类型了。
如果你用的是Maven的话,只需把下面的引用添加到工里面便能够体验下该参考实现的当前功能了:
|
1
2
3
4
5
|
<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工厂来获取:
|
1
2
3
4
5
6
7
|
// 根据货币代码来获取货币单位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的实现必须是不可变,线程安全且可比较的。
|
1
2
3
4
5
6
7
8
9
|
/ get MonetaryAmount from CurrencyUnitCurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR");MonetaryAmount fiveEuro = Money.of(5, euro);// get MonetaryAmount from currency codeMonetaryAmount tenUsDollar = Money.of(10, "USD");// FastMoney is an alternative MonetaryAmount factory that focuses on performanceMonetaryAmount 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的实例:
|
1
2
3
4
|
MonetaryAmount specAmount = MonetaryAmounts.getDefaultAmountFactory() .setNumber(123.45) .setCurrency("USD") .create(); |
当且仅当实现类,货币单位,以及数值全部相等时才认为这两个MontetaryAmount实例是相等的。
|
1
2
3
|
MonetaryAmount oneEuro = Money.of(1, MonetaryCurrencies.getCurrency("EUR"));boolean isEqual = oneEuro.equals(Money.of(1, "EUR")); // trueboolean isEqualFast = oneEuro.equals(FastMoney.of(1, "EUR")); // false |
MonetaryAmount内包含丰富的方法,可以用来获取具体的货币,金额,精度等等:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
MonetaryAmount monetaryAmount = Money.of(123.45, euro);CurrencyUnit currency = monetaryAmount.getCurrency();NumberValue numberValue = monetaryAmount.getNumber();int intValue = numberValue.intValue(); // 123double doubleValue = numberValue.doubleValue(); // 123.45long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45int precision = numberValue.getPrecision(); // 5// NumberValue extends java.lang.Number. // So we assign numberValue to a variable of type NumberNumber number = numberValue; |
MonetaryAmount的使用
可以在MonetaryAmount上进行算术运算:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 NumberValueMonetaryAmount minusTwoEuro = fiveEuro.subtract(sevenEuro); // "EUR -2"// some useful utility methodsboolean greaterThan = sevenEuro.isGreaterThan(fiveEuro); // trueboolean positive = sevenEuro.isPositive(); // trueboolean 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/USDfiveEuro.add(tenUsDollar); |
舍入操作是金额换算里面非常重要的一部分。MonetaryAmount可以使用舍入操作符来进行四舍五入:
|
1
2
3
4
|
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一起配套使用。
看一下下面这个集合:
|
1
2
3
4
5
6
|
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来进行金额过滤:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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()); |
我们还可以过滤出大于或小于某个阈值的金额:
|
1
2
3
4
5
6
7
|
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()); |
排序也是类似的:
|
1
2
3
4
5
6
7
8
9
10
11
|
// 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()); |
还有分组操作:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 按货币单位进行分组// {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 USDMonetarySummaryStatistics 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还提供了归约函数,可以用来获取最大值,最小值,以及求和:
|
1
2
3
4
5
6
7
8
|
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对象。
|
1
2
3
4
5
6
7
8
9
10
11
12
|
// A monetary operator that returns 10% of the input MonetaryAmount// Implemented using Java 8 LambdasMonetaryOperator 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 MonetaryAmountMonetaryAmount 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)的汇率。
|
1
2
3
4
5
6
7
8
9
|
// 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链并将第一个返回准确结果的提供商的数据返回。
|
1
2
3
4
5
6
|
// get the exchange rate from euro to us dollarExchangeRate rate = exchangeRateProvider.getExchangeRate("EUR", "USD");NumberValue factor = rate.getFactor(); // 1.2537 (at time writing)CurrencyUnit baseCurrency = rate.getBaseCurrency(); // EURCurrencyUnit targetCurrency = rate.getCurrency(); // USD |
货币转换
不同货币间的转换可以通过ExchangeRateProvider返回的CurrencyConversions来完成。
|
1
2
3
4
5
6
7
8
9
10
|
// get the CurrencyConversion from the default provider chainCurrencyConversion dollarConversion = MonetaryConversions.getConversion("USD");// get the CurrencyConversion from a specific providerCurrencyConversion 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来与字符串进行解析/格式化。
|
1
2
3
4
5
6
7
8
9
10
11
|
// formatting by locale specific formatsMonetaryAmountFormat 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 stringsMonetaryAmount parsed = germanFormat.parse("12,4 USD"); |
可以通过AmountFormatQueryBuilder来生成自定义的格式。
|
1
2
3
4
5
6
7
8
9
|
// Creating a custom MonetaryAmountFormatMonetaryAmountFormat 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中下载到。
Java 9中新的货币API的更多相关文章
- java 8中新的日期和时间API
java 8中新的日期和时间API 使用LocalDate和LocalTime LocalDate的实例是一个不可变对象,它只提供了简单的日期,并不含当天的时间信息.另外,它也不附带任何与时区相关的信 ...
- Java SE 6 新特性: 编译器 API
新 API 功能简介 JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中.在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤: ...
- Java 8 中新的 Date 和 Time 类入门详解
这篇文章主要是java8中新的Date和Time API的实战.新的Date和Time类是java开发者社区千呼万唤始出来的.Java8 之前存在的Date类一直都受人诟病,很多人都会选择使用第三方的 ...
- Java 代码中如何调用 第三方Api
在代码中调用第三方API 获取数据 package com.example.demo.utils; import com.alibaba.fastjson.JSONObject; import lom ...
- 详解 Java 17 中新推出的密封类
Java 17推出的新特性Sealed Classes经历了2个Preview版本(JDK 15中的JEP 360.JDK 16中的JEP 397),最终定稿于JDK 17中的JEP 409.Seal ...
- JDK8中新日期时间API
它们面临的问题是:可变性:像日期和时间这样的类应该是不可变的.偏移性:Date中的年份是从1900开始的,而月份都从0开始.格式化:格式化只对Date有用,Calendar则不行.此外,它们也不是线程 ...
- Java 9和Java 10的新特性
http://www.infoq.com/cn/news/2014/09/java9 Java 9新特性汇总 继2014年3月份Java 8发布之后,Open JDK加快了开发速度, Java 9的发 ...
- [转] Java 8的新特性
简介 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性.在本文中我们将学习这些新特性,并用实际的例子 ...
- Java 8的新特性—终极版
作者:杜琪[译] 原文链接:http://www.jianshu.com/p/5b800057f2d8 1. 简介 毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本 ...
随机推荐
- hdu 6070 Dirt Ratio 线段树+二分
Dirt Ratio Time Limit: 18000/9000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)Spe ...
- 《深入理解JVM虚拟机》读书笔记
前言:<深入理解JVM虚拟机>是JAVA的经典著作之一,因为内容更偏向底层,所以之前一直没有好好的阅读过.最近因为刚好有空,又有了新目标.所以打算和<构架师的12项修炼>一起看 ...
- Q-Q图
来自:https://mp.weixin.qq.com/s/_UTKNcOgKQcCogk2C2tsQQ 正负样本数据集符合独立同分布是构建机器学习模型的前提,从概率的角度分析,样本数据独立同分布是正 ...
- [osg]OSG相机添加动画路径
查看osg坐标系,camare默认姿态:http://www.cnblogs.com/lyggqm/p/8073688.html 首先搞清楚osg的坐标系以及osg::camare的默认姿态 下代码面 ...
- [osg]osgcallback各种回调使用的例子介绍
观察MyReadFileCallback结构体的内容,可以发现它继承自osgDB::Registry::ReadFileCallback,并重载了一个函数readNode,分析源代码可知,该函数在os ...
- 力扣(LeetCode)231. 2的幂
给定一个整数,编写一个函数来判断它是否是 2 的幂次方. 示例 1: 输入: 1 输出: true 解释: 20 = 1 示例 2: 输入: 16 输出: true 解释: 24 = 16 示例 3: ...
- Android SDK开发
目前我们的应用内使用了 ArcFace 的人脸检测功能,其他的我们并不了解,所以这里就和大家分享一下我们的集成过程和一些使用心得 集成 ArcFace FD 的集成过程非常简单 在 ArcFace F ...
- QTableWidget自定义表头QHeaderView加全选复选框
1 QTableWidget自定义表头QHeaderView加全选复选框 在使用QTableWidget时需要在表头添加全选复选框,但是默认的表头无法添加复选框,只能用图片画上去一个复 ...
- css设置字体单行,多行超出省略号显示
单行: overflow: hidden; text-overflow:ellipsis; white-space: nowrap; 多行 display: -webkit-box; -webkit- ...
- C#内存压缩zip文件
C#中我们使用比较多的文件压缩第三方DLL就是Ionic.Utils.Zip.dll.但是这个DLL只支持对现有文件进行压缩,而不支持内存压缩,如果需要使用内存压缩,那么有第三方DLL ICSharp ...