Java Money and the Currency API – Java货币和货币API

最后修改: 2017年 3月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

JSR 354 – “Currency and Money” addresses the standardization of currencies and monetary amounts in Java.

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

Its goal is to add a flexible and extensible API to the Java ecosystem and make working with monetary amounts simpler and safer.

它的目标是为Java生态系统增加一个灵活和可扩展的API,使货币金额的工作更简单、更安全。

The JSR did not make its way into JDK 9 but is a candidate for future JDK releases.

该JSR没有进入JDK 9,但却是未来JDK版本的一个候选。

2. Setup

2.设置

First, let’s define the dependency into our pom.xml file:

首先,让我们在pom.xml文件中定义这个依赖关系。

<dependency>
    <groupId>org.javamoney</groupId>
    <artifactId>moneta</artifactId>
    <version>1.1</version>
</dependency>

The latest version of the dependency can be checked here.

最新版本的依赖关系可以在这里检查。

3. JSR-354 Features

3.JSR-354的特点

The goals of “Currency and Money” API:

货币与金钱 “API的目标。

  • To provide an API for handling and calculating monetary amounts
  • To define classes representing currencies and monetary amounts, as well as monetary rounding
  • To deal with currency exchange rates
  • To deal with formatting and parsing of currencies and monetary amounts

4. Model

4.模型

Main classes of the JSR-354 specification, are depicted in the following diagram:

JSR-354规范的主要类别在下图中得到了描述。

javax monetary3 1

The model holds two main interfaces CurrencyUnit and MonetaryAmount, explained in the following sections.

该模型拥有两个主要的接口CurrencyUnitMonetaryAmount,在下面的章节中解释。

5. CurrencyUnit

5.CurrencyUnit

CurrencyUnit models the minimal properties of a currency. Its instances can be obtained using the Monetary.getCurrency method:

CurrencyUnit为货币的最小属性建模。它的实例可以通过Monetary.getCurrency方法获得。

@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);
}

We create CurrencyUnit using a String representation of the currency, this could lead to a situation where we try to create a currency with nonexistent code. Creating currencies with nonexistent codes raise an UnknownCurrency exception:

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

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

6. MonetaryAmount

6.MonetaryAmount

MonetaryAmount is a numeric representation of a monetary amount. It’s always associated with CurrencyUnit and defines a monetary representation of a currency.

MonetaryAmount是一个货币金额的数字表示。它总是与CurrencyUnit相关联,并定义了一种货币的货币表示。

The amount can be implemented in different ways, focusing on the behavior of a monetary representation requirements, defined by each concrete use cases. For example. Money and FastMoney are implementations of the MonetaryAmount interface.

金额可以通过不同的方式实现,重点是货币表示要求的行为,由每个具体的用例定义。例如。MoneyFastMoneyMonetaryAmount接口的实现。

FastMoney implements MonetaryAmount using long as numeric representation, and is faster than BigDecimal at the cost of precision; it can be used when we need performance and precision isn’t an issue.

FastMoney使用long作为数字表示法来实现MonetaryAmount,它比BigDecimal更快,但要牺牲精度;当我们需要性能而精度不是问题时,可以使用它。

A generic instance can be created using a default factory. Let’s show the different way of obtaining MonetaryAmount instances:

可以使用一个默认的工厂来创建一个通用的实例。让我们展示一下获得MonetaryAmount实例的不同方式。

@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. Monetary Arithmetic

7.货币算术

We can perform monetary arithmetic between Money and FastMoney but we need to be careful when we combine instances of these two classes.

我们可以在MoneyFastMoney之间进行货币运算,但是当我们结合这两个类的实例时,需要小心。

For example, when we compare one Euro instance of FastMoney with one Euro instance of Money the result is that they are not the same:

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

@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")));
}

We can perform add, subtract, multiply, divide and other monetary arithmetic operations using the methods provided by the MonetaryAmount class.

我们可以使用MonetaryAmount类提供的方法进行加、减、乘、除和其他货币算术操作。

Arithmetic operations should throw an ArithmeticException, if the arithmetic operations between amounts outperform the capabilities of the numeric representation type used, for example, if we try to divide one by three, we get an ArithmeticException because the result is an infinite number:

如果金额之间的算术运算超过了所使用的数字表示类型的能力,那么算术运算应该抛出一个ArithmeticException,例如,如果我们试图用1除以3,我们会得到一个ArithmeticException,因为结果是一个无限的数字。

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

When adding or subtracting amounts, it’s better to use parameters which are instances of MonetaryAmount, as we need to ensure that both amounts have the same currency to perform operations between amounts.

当加减金额时,最好使用作为MonetaryAmount实例的参数,因为我们需要确保两个金额具有相同的货币,以便在金额之间执行操作。

7.1. Calculating Amounts

7.1.计算金额

A total of amounts can be calculated in multiple ways, one way is simply to chain the amounts with:

金额的总和可以用多种方式计算,一种方式是简单地将金额与之连锁。

@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());
}

Chaining can also be applied to subtracting:

链式计算也可以应用于减法。

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

Multiplying:

乘法。

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Or dividing:

或分割。

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Let’s compare our arithmetic results using Strings, given that with Strings because the result also contains the currency:

让我们用字符串来比较我们的算术结果,鉴于用字符串是因为结果也包含货币。

@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 Rounding

8.货币的四舍五入

Monetary rounding is nothing else than a conversion from an amount with an undetermined precision to a rounded amount.

货币四舍五入无非是从一个未确定精度的金额转换为一个四舍五入的金额。

We’ll use the getDefaultRounding API provided by the Monetary class to make the conversion. The default rounding values are provided by the currency:

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

@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. Currency Conversion

9.货币转换

Currency conversion is an important aspect of dealing with money. Unfortunately, these conversions have a great variety of different implementations and use cases.

货币转换是处理金钱的一个重要方面。不幸的是,这些转换有大量不同的实现方式和使用情况。

The API focuses on the common aspects of currency conversion based on the source, target currency, and exchange rate.

该API重点关注基于源货币、目标货币和汇率的货币转换的常见方面。

Currency conversion or the access of exchange rates can be parametrized:

货币转换或汇率的获取可以被参数化。

@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);
}

A conversion is always bound to currency. MonetaryAmount can simply be converted by passing a CurrencyConversion to the amount’s with method.

一个转换总是与货币绑定。MonetaryAmount可以简单地通过传递一个CurrencyConversion到金额的with方法进行转换。

10. Currency Formatting

10.货币格式化

The formatting allows the access of formats based on java.util.Locale. Contrary to the JDK, the formatters defined by this API are thread-safe:

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

@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);
}

Here we’re using the predefined format and creating a custom format for our currencies. The use of the standard format is straightforward using the method format of the MonetaryFormats class. We defined our custom format setting the pattern property of the format query builder.

这里我们使用预定义的格式,为我们的货币创建一个自定义的格式。使用标准格式是直接使用MonetaryFormats类的方法格式。我们定义了我们的自定义格式,设置了格式查询生成器的模式属性。

As before because the currency is included in the result we test our results using Strings:

和以前一样,因为货币包含在结果中,我们用字符串来测试我们的结果。

@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. Summary

11.总结

In this quick article, we’ve covered the basics of the Java Money & Currency JSR.

在这篇快速的文章中,我们已经介绍了Java货币和货币JSR的基本知识。

Monetary values are used everywhere, and Java provides is starting to support and handle monetary values, arithmetic or currency conversion.

货币价值的使用无处不在,而Java提供的是开始支持和处理货币价值、算术或货币转换。

As always, you can find the code from the article over on Github.

一如既往,你可以从Github上的文章中找到相关代码。