阅读更多

1顶
0踩

编程语言

转载新闻 Java 9中新的货币API

2015-01-08 17:14 by 正式编辑 cao345657340 评论(3) 有6517人浏览
摘要:此前,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 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()); //  

自定义的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中看到这套新的接口!

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

原文出处:Michael Scharhag
来自: Java译站
1
0
评论 共 3 条 请登录后发表评论
3 楼 gotyou 2015-01-09 15:01
还在用jdk1.4的表示完全跟不上节奏了.
2 楼 QuarterLifeForJava 2015-01-09 10:46
somefuture 写道
jdk8的API还没学习呢,就。。

项目用的还是6呢。。。还在学7呢。。。。
1 楼 somefuture 2015-01-09 10:06
jdk8的API还没学习呢,就。。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Java for Android 基础API整理

    API简析 *application programming interface 应用编程接口 *一切能调用的东西   Java.lang包  Lang=language  语言包  自动导包  String  System

  • Java-Day16 日期和时间相关API、数字格式化相关API

    Java学习的第16天,1.... JDK8中新日期时间API(LocalDate、LocalTime、LocalDateTime、瞬时:Instant、DateTimeFormatter类、其他API),3.DecimalFormat和NumberFormat等,4. BigInteger与BigDecimal...

  • java9终极特性

    Jigsaw项目是为了模块化Java代码、将JRE分成可相互协作的组件,这也是Java 9 众多特色种的一个。JEP是迈向Jigsaw四步中的第一步,它不会改变JRE和JDK的真实结构。JEP是为了模块化JDK源代码,让编译系统能够模块编译...

  • Java高并发秒杀API-Java高并发秒杀APi之业务分析与DAO层代码编写

    1、Java高并发秒杀API之业务分析与DAO层 2、Java高并发秒杀API之web层 3、Java高并发秒杀API之Service层 4、Java高并发秒杀API之高并发优化 其实这几个流程也就是开发的流程,首先从DAO层开始开发,从后往前开发,开始...

  • Java基础-Java 9&10&11新特性

    Java9提供了超过150项新功能特性,包括模块化系统、可交互的REPL工具:jshell、JDK编译工具,Java公共API和私有代码,以及安全增强、扩展提升、性能管理改善等 JDK和JRE目录结构改变:JDK8: /bin /include /jre /lib...

  • Java面试不通过?这篇文章你看了吗?

    Java面试题千千万,个人觉得没有最好的答案,只有最适合的答案;本文的宗旨是为读者朋友们整理一份详细而又权威的面试清单。 此文是前段时间本人根据部分文章汇总压在草稿箱(由于时间关系,忘了加上对应的原创链接...

  • 进程列表 java 源码_Java9 终极特性列表

    原标题:Java9 终极特性列表1、模块化的源代码Jigsaw项目是为了模块化Java代码、将JRE分成可相互协作的组件,这也是Java 9 众多特色种的一个。JEP是迈向Jigsaw四步中的第一步,它不会改变JRE和JDK的真实结构。JEP是...

  • 那些年我错过的Java

    正研究怎么升级到Java 9尝尝鲜,结果发现再有80天Java 11都要发布了!真是山中方一日,世上已千年。才不关注Java没多久,已经快成版本帝了。索性直接上了Java 10,把最近落下的都补上。在这个夏天,补上那些年我错过...

  • 三、Java 10 新特性

    作为当今使用最广泛的编程语言之一的 Java 在 2018 年 3 月 21 日发布了第十个大版本。为了更快地迭代、更好地跟进社区反馈,Java 语言版本发布周期调整为每隔 6 个月发布一次。Java 10 是这一新规则之后,采用新...

  • java日期系统

    还不会用jdk8日期时间系统?...- 了解并掌握常用的Java时间相关的类与方法。 - 能够在需要使用的时候知道大致有哪些方法,然后知道可以使用这些方法实现自己想要的功能。 - 能够熟练地去JDKAPI中进行查找相关方法。

  • JAVA7-9个版本特性汇总

    Java新特性一、 Java7:Java 7 的架构图:新特性一览表:Swing· 新增 JLayer 类,是一个灵活而且功能强大的Swing组件修饰器,使用方法:How to DecorateComponents with JLayer.· Nimbus Look andFeel 外观...

  • Java8-为什么选择java8

    Java的演变过程 Java从头到尾都是被设计成为一门面向对象的语言,所以时间长了,它就积累了很多的有用的库。从头开始,他就拥有多线程处理的能力。最重要的是Java里面有两个非常强大非常超前的两个概念:jvm和Java...

  • 2020最新整理JAVA面试题附答案

    包含的模块:本文分为十九个模块,分别是:Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、...

  • Unity Terrain Adjust

    核心特性:地形调整的灵活性 地形高度与坡度调整: 利用Terrain Adjust,设计师可以根据需要轻松调整地形的高度和坡度,创造出更加自然和真实的环境。 光滑边缘处理: 工具提供了边缘平滑功能,确保地形调整后的过渡自然,避免了突兀的高低变化。 自定义画笔设置: 可调整画笔大小、衰减、间距等参数,让设计师能够精确控制地形的每一个细节。 应用场景:多样化的地形创作 道路与岩石融合: 利用Terrain Adjust,可以将道路和岩石自然地混合到地形中,为游戏世界增添更多细节。 坡道创建: 工具还支持创建坡道,为游戏中的车辆或其他移动元素提供更加丰富的地形变化。 技术细节:轻量级与高效 编辑器专用: 作为编辑器的专用工具,Terrain Adjust不会对项目造成混乱,保持了工作环境的整洁。 Collider需求: 为了使用Terrain Adjust,目标对象需要有Collider组件,以确保地形调整的准确性。 Terrain Adjust工具以其轻量级设计和强大的地形调整功能,成为了Unity环境设计师的得力助手。它不仅提高了工作效率,还为创造更加丰富和真实的游戏世界提供了可能。

  • 基于 Shell 的驾照理论考试练习软件的设计与实现

    【作品名称】:基于 Shell 的驾照理论考试练习软件的设计与实现 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】: 测试题数据存储设计 # 测试题目文件夹 # 每个测试题作为一个目录,目录下面必须有 content.txt、options.txt 和 answer.txt 三个文件 # content.txt 文件内容为题目内容 # options.txt 文件内容为题目选项,每个选项占一行 # answer.txt 文件内容为正确答案 export tests_folder='./tests' 复习错题集自动删除答对的错题 export failed_list_file='failed.txt' # 错题集文件 sed -i '' "/$test/d" $failed_list_file

  • PiP-Tool.msi

    PiP-Tool

  • node-v0.10.42-sunos-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

  • 【毕业设计】YOLOv9 QT+NCNN实现安卓端部署源码+部署步骤+演示apk.zip

    高分毕业设计源码 基于YOLO的毕业选题设计的程序源码,适用与计算机与软件工程毕业设计选题

  • node-v0.10.28-sunos-x86.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

Global site tag (gtag.js) - Google Analytics