Java Money et l’API de devise

Java Money et l'API de devise

1. Vue d'ensemble

JSR 354 - «Currency and Money» traite de la normalisation des devises et des montants monétaires en Java.

Son objectif est d'ajouter une API flexible et extensible à l'écosystème Java et de rendre le travail avec des montants monétaires plus simple et plus sûr.

Le JSR n'a pas fait son chemin dans JDK 9 mais est un candidat pour les futures versions de JDK.

2. Installer

Tout d'abord, définissons la dépendance dans notre fichierpom.xml:


    org.javamoney
    moneta
    1.1

La dernière version de la dépendance peut être vérifiéehere.

3. Caractéristiques du JSR-354

Les objectifs de l’API «Monnaie et argent»:

  • Fournir une API pour manipuler et calculer les montants

  • Définir des classes représentant des devises et des montants monétaires, ainsi que des arrondis monétaires

  • Pour faire face aux taux de change

  • Traiter le formatage et l'analyse des devises et des montants

4. Modèle

Les principales classes de la spécification JSR-354 sont décrites dans le diagramme suivant:

image

Le modèle contient deux interfaces principalesCurrencyUnit etMonetaryAmount, expliquées dans les sections suivantes.

5. CurrencyUnit

CurrencyUnit modélise les propriétés minimales d'une devise. Ses instances peuvent être obtenues en utilisant la méthodeMonetary.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);
}

Nous créonsCurrencyUnit en utilisant une représentationString de la devise, cela pourrait conduire à une situation où nous essayons de créer une devise avec un code inexistant. La création de devises avec des codes inexistants déclenche une exceptionUnknownCurrency:

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

6. MonetaryAmount

MonetaryAmount est une représentation numérique d'un montant monétaire. Il est toujours associé àCurrencyUnit et définit une représentation monétaire d'une devise.

Le montant peut être mis en œuvre de différentes manières, en se concentrant sur le comportement d'une exigence de représentation monétaire, définie par chaque cas d'utilisation concret. Par exemple. Money etFastMoney sont des implémentations de l'interfaceMonetaryAmount.

FastMoney implémenteMonetaryAmount en utilisantlong comme représentation numérique, et est plus rapide queBigDecimal au détriment de la précision; il peut être utilisé lorsque nous avons besoin de performances et que la précision n’est pas un problème.

Une instance générique peut être créée à l'aide d'une fabrique par défaut. Voyons les différentes manières d'obtenir des instancesMonetaryAmount:

@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

Nous pouvons effectuer de l'arithmétique monétaire entreMoney etFastMoney mais nous devons être prudents lorsque nous combinons des instances de ces deux classes.

Par exemple, lorsque nous comparons une instance Euro deFastMoney avec une instance Euro deMoney, le résultat est qu'elles ne sont pas identiques:

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

Nous pouvons effectuer des opérations d’addition, de soustraction, de multiplication, de division et d’autres opérations arithmétiques monétaires en utilisant les méthodes fournies par la classeMonetaryAmount.

Les opérations arithmétiques devraient lancer unArithmeticException, si les opérations arithmétiques entre les montants surpassent les capacités du type de représentation numérique utilisé, par exemple, si nous essayons de diviser un par trois, nous obtenons unArithmeticException car le résultat est un nombre infini:

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

Lors de l'ajout ou de la soustraction de montants, il est préférable d'utiliser des paramètres qui sont des instances deMonetaryAmount, car nous devons nous assurer que les deux montants ont la même devise pour effectuer des opérations entre les montants.

7.1. Calcul des montants

Un total de montants peut être calculé de plusieurs manières, une méthode consiste simplement à chaîner les montants avec:

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

Le chaînage peut également être appliqué à la soustraction:

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

Multipliant:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Ou diviser:

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Comparons nos résultats arithmétiques en utilisant Strings, étant donné que avec Strings car le résultat contient également la devise:

@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. Arrondi monétaire

L’arrondissement monétaire n’est rien d’autre que la conversion d’un montant dont la précision est indéterminée en un montant arrondi.

Nous utiliserons l'APIgetDefaultRounding fournie par la classeMonetary pour effectuer la conversion. Les valeurs d'arrondi par défaut sont fournies par la devise:

@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. Conversion de devises

La conversion des devises est un aspect important de la gestion de l'argent. Malheureusement, ces conversions ont une grande variété d'implémentations et de cas d'utilisation différents.

L'API se concentre sur les aspects communs de la conversion de devise en fonction de la source, de la devise cible et du taux de change.

La conversion monétaire ou l'accès aux taux de change peuvent être paramétrés:

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

Une conversion est toujours liée à la devise. MonetaryAmount peut simplement être converti en passant unCurrencyConversion à la méthodewith du montant.

10. Formatage des devises

Le formatage permet l'accès aux formats basés surjava.util.Locale. Contrairement au JDK, les formateurs définis par cette API sont thread-safe:

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

Ici, nous utilisons le format prédéfini et créons un format personnalisé pour nos devises. L'utilisation du format standard est simple en utilisant le format de méthode de la classeMonetaryFormats. Nous avons défini notre format personnalisé en définissant la propriété pattern du générateur de requête de format.

Comme auparavant car la devise est incluse dans le résultat, nous testons nos résultats en utilisantStrings:

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

Dans cet article rapide, nous avons abordé les bases de Java Money & Currency JSR.

Les valeurs monétaires sont utilisées partout et Java fournit commence à prendre en charge et à gérer les valeurs monétaires, l’arithmétique ou la conversion monétaire.

Comme toujours, vous pouvez trouver le code de l'articleover on Github.