Java Money и валютный API

Java Money и валютный API

1. обзор

JSR 354 - «Валюта и деньги» касается стандартизации валют и денежных сумм в Java.

Его цель - добавить гибкий и расширяемый API в экосистему Java и сделать работу с денежными суммами проще и безопаснее.

JSR не вошел в JDK 9, но является кандидатом для будущих выпусков JDK.

2. Настроить

Во-первых, давайте определим зависимость в нашем файлеpom.xml:


    org.javamoney
    moneta
    1.1

Последнюю версию зависимости можно проверитьhere.

3. Характеристики JSR-354

Цели API «Валюта и деньги»:

  • Предоставить API для обработки и расчета денежных сумм

  • Определить классы, представляющие валюты и денежные суммы, а также денежное округление

  • Иметь дело с курсами валют

  • Иметь дело с форматированием и парсингом валют и денежных сумм

4. модель

Основные классы спецификации JSR-354 изображены на следующей диаграмме:

image

Модель содержит два основных интерфейсаCurrencyUnit иMonetaryAmount,, которые описаны в следующих разделах.

5. CurrencyUnitс

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

Мы создаемCurrencyUnit, используя представление валютыString, это может привести к ситуации, когда мы пытаемся создать валюту с несуществующим кодом. Создание валют с несуществующими кодами вызывает исключениеUnknownCurrency:

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

6. MonetaryAmountс

MonetaryAmount - это числовое представление денежной суммы. Он всегда связан сCurrencyUnit и определяет денежное представление валюты.

Сумма может быть реализована различными способами, ориентируясь на поведение требований к денежному представлению, определяемых каждым конкретным вариантом использования. Например. Money иFastMoney являются реализациями интерфейсаMonetaryAmount.

FastMoney реализуетMonetaryAmount, используяlong в качестве числового представления, и быстрее, чемBigDecimal, за счет точности; его можно использовать, когда нам нужна производительность, и точность не является проблемой.

Общий экземпляр может быть создан с использованием фабрики по умолчанию. Покажем другой способ получения экземпляров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с

Мы можем выполнять денежную арифметику междуMoney иFastMoney, но нам нужно быть осторожными при объединении экземпляров этих двух классов.

Например, когда мы сравниваем один экземпляр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")));
}

Мы можем выполнять сложение, вычитание, умножение, деление и другие денежные арифметические операции, используя методы, предоставляемые классомMonetaryAmount.

Арифметические операции должны выдаватьArithmeticException, если арифметические операции между суммами превосходят возможности используемого типа числового представления, например, если мы пытаемся разделить единицу на три, мы получаемArithmeticException, потому что результат бесконечное число:

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

При добавлении или вычитании сумм лучше использовать параметры, являющиеся экземплярамиMonetaryAmount, так как нам нужно убедиться, что обе суммы имеют одинаковую валюту для выполнения операций между суммами.

7.1. Расчет сумм

Итого суммы могут быть рассчитаны несколькими способами, один из способов - просто связать суммы с:

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

Цепочка также может применяться для вычитания:

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

Умножение:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Или деление:

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Давайте сравним наши арифметические результаты с использованием строк, учитывая, что это со строками, потому что результат также содержит валюту:

@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. Денежное округление

Денежное округление - это не что иное, как преобразование суммы с неопределенной точностью в округленную сумму.

Мы будем использовать APIgetDefaultRounding, предоставляемый классомMonetary, для выполнения преобразования. Значения округления по умолчанию предоставляются валютой:

@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 фокусируется на общих аспектах конвертации валюты на основе источника, целевой валюты и обменного курса.

Конвертация валюты или доступ к обменным курсам может быть параметризован:

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

Конвертация всегда привязана к валюте. MonetaryAmount можно просто преобразовать, передавCurrencyConversion методу суммыwith.

10. Форматирование валюты

Форматирование обеспечивает доступ к форматам на основе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);
}

Здесь мы используем предопределенный формат и создаем собственный формат для наших валют. Использование стандартного формата просто с использованием формата метода классаMonetaryFormats. Мы определили наш собственный формат, установив свойство pattern в построителе запросов форматирования.

Как и раньше, поскольку валюта включена в результат, мы тестируем наши результаты с помощью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. Резюме

В этой быстрой статье мы рассмотрели основы Java Money & Currency JSR.

Денежные значения используются повсеместно, и Java предоставляет поддержку и обработку денежных значений, арифметики или конвертации валют.

Как всегда, вы можете найти код из статьиover on Github.