Java Money e a API da moeda

Java Money e a API da moeda

1. Visão geral

JSR 354 - “Moeda e dinheiro” trata da padronização de moedas e valores monetários em Java.

Seu objetivo é adicionar uma API flexível e extensível ao ecossistema Java e tornar o trabalho com valores monetários mais simples e seguro.

O JSR não entrou no JDK 9, mas é um candidato para versões futuras do JDK.

2. Configuração

Primeiro, vamos definir a dependência em nosso arquivopom.xml:


    org.javamoney
    moneta
    1.1

A versão mais recente da dependência pode ser verificadahere.

3. Recursos JSR-354

Os objetivos da API "Moeda e dinheiro":

  • Para fornecer uma API para manipular e calcular valores monetários

  • Definir classes que representam moedas e valores monetários, bem como arredondamentos monetários

  • Para lidar com taxas de câmbio

  • Para lidar com a formatação e análise de moedas e valores monetários

4. Modelo

As principais classes da especificação JSR-354 estão representadas no diagrama a seguir:

image

O modelo contém duas interfaces principaisCurrencyUnit eMonetaryAmount, explicadas nas seções a seguir.

5. CurrencyUnit

CurrencyUnit modela as propriedades mínimas de uma moeda. Suas instâncias podem ser obtidas usando o métodoMonetary.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);
}

CriamosCurrencyUnit usando uma representaçãoString da moeda, isso pode levar a uma situação em que tentamos criar uma moeda com código inexistente. A criação de moedas com códigos inexistentes gera uma exceçãoUnknownCurrency:

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

6. MonetaryAmount

MonetaryAmount é uma representação numérica de uma quantia monetária. Está sempre associado aCurrencyUnit e define uma representação monetária de uma moeda.

O valor pode ser implementado de diferentes maneiras, com foco no comportamento de requisitos de representação monetária, definido por cada caso de uso concreto. Por exemplo. MoneyeFastMoney são implementações da interfaceMonetaryAmount.

FastMoney implementaMonetaryAmount usandolong como representação numérica e é mais rápido queBigDecimal ao custo da precisão; pode ser usado quando precisamos de desempenho e a precisão não é um problema.

Uma instância genérica pode ser criada usando uma fábrica padrão. Vamos mostrar a forma diferente de obter instâncias deMonetaryAmount:

@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

Podemos realizar aritmética monetária entreMoneyeFastMoney, mas precisamos ter cuidado ao combinar instâncias dessas duas classes.

Por exemplo, quando comparamos uma instância de Euro deFastMoney com uma instância de Euro deMoney, o resultado é que eles não são iguais:

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

Podemos realizar adicionar, subtrair, multiplicar, dividir e outras operações aritméticas monetárias usando os métodos fornecidos pela classeMonetaryAmount.

As operações aritméticas devem lançar umArithmeticException, se as operações aritméticas entre os valores superam as capacidades do tipo de representação numérica usado, por exemplo, se tentarmos dividir um por três, obtemos umArithmeticException porque o resultado é um número infinito:

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

Ao adicionar ou subtrair valores, é melhor usar parâmetros que são instâncias deMonetaryAmount, pois precisamos garantir que ambos os valores tenham a mesma moeda para realizar operações entre os valores.

7.1. Cálculo de valores

Um total de valores pode ser calculado de várias maneiras, uma maneira é simplesmente encadear os valores com:

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

O encadeamento também pode ser aplicado à subtração:

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

Multiplicando:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Ou dividindo:

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Vamos comparar nossos resultados aritméticos usando Strings, dado que com Strings porque o resultado também contém a moeda:

@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. Arredondamento monetário

O arredondamento monetário nada mais é do que uma conversão de um valor com uma precisão indeterminada para um valor arredondado.

Usaremos a APIgetDefaultRounding fornecida pela classeMonetary para fazer a conversão. Os valores de arredondamento padrão são fornecidos pela moeda:

@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. conversão de moeda

A conversão de moeda é um aspecto importante para lidar com dinheiro. Infelizmente, essas conversões têm uma grande variedade de implementações e casos de uso diferentes.

A API concentra-se nos aspectos comuns da conversão de moeda com base na origem, moeda de destino e taxa de câmbio.

A conversão de moeda ou o acesso a taxas de câmbio podem ser parametrizados:

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

Uma conversão está sempre vinculada à moeda. MonetaryAmount pode simplesmente ser convertido passando umCurrencyConversion para o métodowith da quantidade.

10. Formatação de Moeda

A formatação permite o acesso a formatos baseados emjava.util.Locale. Ao contrário do JDK, os formatadores definidos por esta API são seguros para threads:

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

Aqui, estamos usando o formato predefinido e criando um formato personalizado para nossas moedas. O uso do formato padrão é direto usando o formato de método da classeMonetaryFormats. Definimos nosso formato personalizado configurando a propriedade padrão do construtor de consultas de formato.

Como antes, porque a moeda está incluída no resultado, testamos nossos resultados usandoStrings:

@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. Sumário

Neste artigo rápido, cobrimos os fundamentos do Java Money & Currency JSR.

Os valores monetários são usados ​​em todos os lugares, e o Java fornece está começando a suportar e manipular valores monetários, aritméticos ou de conversão de moeda.

Como sempre, você pode encontrar o código no artigoover on Github.