关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »Java 9中新的货币API

    Java 9中新的货币API

    2015-01-10 00:00:00 出处:InfoQ - 吴海星
    分享

    JSR 354定义了一套新的Java货币API,计划会在Java 9中正式引入。ITJS的这篇文章中我们将来看一下它的参考实现:JavaMoney的当前进展。

    正如我在之前那篇Java 8新的日期时间API一文中那样,ITJS的这篇文章主要也是通过一些代码来演示下新的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中下载到。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    最全面的前端开发指南

    Windows7任务栏桌面下角的一些正在运行的图标不见了

    sql server快速删除记录方法

    SQL Server 7移动数据的6种方法

    SQL Server 2008的新压缩特性

    每个Java程序员必须知道的5个JVM命令行标志

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。