Java Money和Currency API

Java Money and the Currency API

1.概述

JSR 354 –"货币和金钱"解决了Java中货币和货币金额的标准化问题。

它的目标是向Java生态系统中添加灵活且可扩展的API,并使使用金额更简单,更安全。

JSR并未进入JDK 9,但已成为将来JDK版本的候选者。

2.设定

首先,让我们在pom.xml文件中定义依赖项:

1
2
3
4
5
<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.1</version>
</dependency>

可以在此处检查依赖项的最新版本。

3. JSR-354的功能

"货币和金钱" API的目标:

  • 提供用于处理和计算金额的API

  • 定义代表货币和货币金额以及货币舍入的类

  • 处理货币汇率

  • 处理货币和货币量的格式和解析

  • 4.型号

    下图描述了JSR-354规范的主要类别:

     width=

    该模型包含两个主要接口CurrencyUnit和MonetaryAmount,以下各节对此进行了说明。

    5. CurrencyUnit

    CurrencyUnit对货币的最小属性进行建模。 可以使用Monetary.getCurrency方法获取其实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void givenCurrencyCode_whenString_thanExist() {
        CurrencyUnit usd = Monetary.getCurrency("USD");

        assertNotNull(usd);
        assertEquals(usd.getCurrencyCode(),"USD");
        assertEquals(usd.getNumericCode(), 840);
        assertEquals(usd.getDefaultFractionDigits(), 2);
    }

    我们使用货币的String表示形式创建CurrencyUnit,这可能会导致我们尝试创建不存在代码的货币的情况。 使用不存在的代码创建货币会引发UnknownCurrency异常:

    1
    2
    3
    4
    @Test(expected = UnknownCurrencyException.class)
    public void givenCurrencyCode_whenNoExist_thanThrowsError() {
        Monetary.getCurrency("AAA");
    }

    6.货币金额

    MonetaryAmount是货币金额的数字表示。 它始终与CurrencyUnit关联,并定义货币的货币表示形式。

    金额可以以不同的方式实现,着重于每个具体用例定义的货币表示需求的行为。 例如。 Money和FastMoney是MonetaryAmount接口的实现。

    FastMoney使用long作为数字表示形式来实现MonetaryAmount,并且以精度为代价比BigDecimal更快。 当我们需要性能且精度不是问题时,可以使用它。

    可以使用默认工厂创建通用实例。 让我们展示获取MonetaryAmount实例的不同方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void givenAmounts_whenStringified_thanEquals() {
     
        CurrencyUnit usd = Monetary.getCurrency("USD");
        MonetaryAmount fstAmtUSD = Monetary.getDefaultAmountFactory()
          .setCurrency(usd).setNumber(200).create();
        Money moneyof = Money.of(12, usd);
        FastMoney fastmoneyof = FastMoney.of(2, usd);

        assertEquals("USD", usd.toString());
        assertEquals("USD 200", fstAmtUSD.toString());
        assertEquals("USD 12", moneyof.toString());
        assertEquals("USD 2.00000", fastmoneyof.toString());
    }

    7.货币算术

    我们可以在Money和FastMoney之间执行货币算术运算,但是当我们组合这两个类的实例时需要小心。

    例如,当我们比较FastMoney的一个欧元实例和Money的一个欧元实例时,结果是它们不相同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void givenCurrencies_whenCompared_thanNotequal() {
        MonetaryAmount oneDolar = Monetary.getDefaultAmountFactory()
          .setCurrency("USD").setNumber(1).create();
        Money oneEuro = Money.of(1,"EUR");

        assertFalse(oneEuro.equals(FastMoney.of(1,"EUR")));
        assertTrue(oneDolar.equals(Money.of(1,"USD")));
    }

    我们可以使用MonetaryAmount类提供的方法执行加,减,乘,除和其他货币算术运算。

    算术运算应抛出ArithmeticException,如果数量之间的算术运算优于所使用的数字表示类型的功能,例如,如果我们尝试将其除以三,则会得到ArithmeticException,因为结果是一个无穷大数:

    1
    2
    3
    4
    5
    6
    @Test(expected = ArithmeticException.class)
    public void givenAmount_whenDivided_thanThrowsException() {
        MonetaryAmount oneDolar = Monetary.getDefaultAmountFactory()
          .setCurrency("USD").setNumber(1).create();
        oneDolar.divide(3);
    }

    在增加或减少金额时,最好使用作为MonetaryAmount实例的参数,因为我们需要确保两个金额使用相同的货币来执行金额之间的运算。

    7.1。 计算金额

    可以使用多种方法来计算总金额,一种方法是简单地将金额链接到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void givenAmounts_whenSummed_thanCorrect() {
        MonetaryAmount[] monetaryAmounts = new MonetaryAmount[] {
          Money.of(100,"CHF"), Money.of(10.20,"CHF"), Money.of(1.15,"CHF")};

        Money sumAmtCHF = Money.of(0,"CHF");
        for (MonetaryAmount monetaryAmount : monetaryAmounts) {
            sumAmtCHF = sumAmtCHF.add(monetaryAmount);
        }

        assertEquals("CHF 111.35", sumAmtCHF.toString());
    }

    链接也可以应用于减法:

    1
    Money calcAmtUSD = Money.of(1,"USD").subtract(fstAmtUSD);

    相乘:

    1
    MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

    或除法:

    1
    MonetaryAmount divideAmount = oneDolar.divide(0.25);

    让我们比较一下使用Strings的算术结果,并假设使用Strings,因为结果还包含货币:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Test
    public void givenArithmetic_whenStringified_thanEqualsAmount() {
        CurrencyUnit usd = Monetary.getCurrency("USD");

        Money moneyof = Money.of(12, usd);
        MonetaryAmount fstAmtUSD = Monetary.getDefaultAmountFactory()
          .setCurrency(usd).setNumber(200.50).create();
        MonetaryAmount oneDolar = Monetary.getDefaultAmountFactory()
          .setCurrency("USD").setNumber(1).create();
        Money subtractedAmount = Money.of(1,"USD").subtract(fstAmtUSD);
        MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);
        MonetaryAmount divideAmount = oneDolar.divide(0.25);

        assertEquals("USD", usd.toString());
        assertEquals("USD 1", oneDolar.toString());
        assertEquals("USD 200.5", fstAmtUSD.toString());
        assertEquals("USD 12", moneyof.toString());
        assertEquals("USD -199.5", subtractedAmount.toString());
        assertEquals("USD 0.25", multiplyAmount.toString());
        assertEquals("USD 4", divideAmount.toString());
    }

    8.货币舍入

    货币舍入只不过是将精度不确定的金额转换为舍入金额。

    我们将使用Monetary类提供的getDefaultRounding API进行转换。 默认舍入值由货币提供:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void givenAmount_whenRounded_thanEquals() {
        MonetaryAmount fstAmtEUR = Monetary.getDefaultAmountFactory()
          .setCurrency("EUR").setNumber(1.30473908).create();
        MonetaryAmount roundEUR = fstAmtEUR.with(Monetary.getDefaultRounding());
       
        assertEquals("EUR 1.30473908", fstAmtEUR.toString());
        assertEquals("EUR 1.3", roundEUR.toString());
    }

    9.货币换算

    货币转换是处理货币的重要方面。 不幸的是,这些转换具有多种不同的实现和用例。

    该API专注于基于来源,目标货币和汇率的货币转换的常见方面。

    可以将货币换算或汇率的获取参数化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void givenAmount_whenConversion_thenNotNull() {
        MonetaryAmount oneDollar = Monetary.getDefaultAmountFactory().setCurrency("USD")
          .setNumber(1).create();

        CurrencyConversion conversionEUR = MonetaryConversions.getConversion("EUR");

        MonetaryAmount convertedAmountUSDtoEUR = oneDollar.with(conversionEUR);

        assertEquals("USD 1", oneDollar.toString());
        assertNotNull(convertedAmountUSDtoEUR);
    }

    转换始终与货币绑定。 通过将CurrencyConversion传递给金额的with方法,可以简单地转换MonetaryAmount。

    10.货币格式

    格式化允许访问基于java.util.Locale的格式。 与JDK相反,此API定义的格式化程序是线程安全的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void givenLocale_whenFormatted_thanEquals() {
        MonetaryAmount oneDollar = Monetary.getDefaultAmountFactory()
          .setCurrency("USD").setNumber(1).create();

        MonetaryAmountFormat formatUSD = MonetaryFormats.getAmountFormat(Locale.US);
        String usFormatted = formatUSD.format(oneDollar);

        assertEquals("USD 1", oneDollar.toString());
        assertNotNull(formatUSD);
        assertEquals("USD1.00", usFormatted);
    }

    在这里,我们使用预定义的格式并为我们的货币创建自定义格式。 使用MonetaryFormats类的方法格式,可以直接使用标准格式。 我们定义了自定义格式,设置了格式查询构建器的pattern属性。

    和以前一样,因为结果中包含货币,所以我们使用Strings测试结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void givenAmount_whenCustomFormat_thanEquals() {
        MonetaryAmount oneDollar = Monetary.getDefaultAmountFactory()
                .setCurrency("USD").setNumber(1).create();

        MonetaryAmountFormat customFormat = MonetaryFormats.getAmountFormat(AmountFormatQueryBuilder.
          of(Locale.US).set(CurrencyStyle.NAME).set("pattern","00000.00 ¤").build());
        String customFormatted = customFormat.format(oneDollar);

        assertNotNull(customFormat);
        assertEquals("USD 1", oneDollar.toString());
        assertEquals("00001.00 US Dollar", customFormatted);
    }

    11.总结

    在这篇快速文章中,我们介绍了Java Money&Currency JSR的基础知识。

    货币值无处不在,并且Java提供了开始支持和处理货币值,算术或货币转换的功能。

    与往常一样,您可以在Github上的文章中找到代码。