Java Money und die Währungs-API

Java Money und die Currency API

1. Überblick

JSR 354 - „Währung und Geld“ befasst sich mit der Standardisierung von Währungen und Geldbeträgen in Java.

Ziel ist es, dem Java-Ökosystem eine flexible und erweiterbare API hinzuzufügen und die Arbeit mit Geldbeträgen einfacher und sicherer zu machen.

Das JSR hat seinen Weg in JDK 9 nicht gefunden, ist jedoch ein Kandidat für zukünftige JDK-Versionen.

2. Konfiguration

Definieren wir zunächst die Abhängigkeit in unsererpom.xml-Datei:


    org.javamoney
    moneta
    1.1

Die neueste Version der Abhängigkeit kann inhere überprüft werden.

3. JSR-354-Funktionen

Die Ziele der API "Währung und Geld":

  • Bereitstellung einer API für die Verwaltung und Berechnung von Geldbeträgen

  • Definieren von Klassen für Währungen und Geldbeträge sowie für die Geldrundung

  • Umgang mit Wechselkursen

  • Formatierung und Analyse von Währungen und Geldbeträgen

4. Modell

Die Hauptklassen der JSR-354-Spezifikation sind in der folgenden Abbildung dargestellt:

image

Das Modell enthält zwei HauptschnittstellenCurrencyUnit undMonetaryAmount,, die in den folgenden Abschnitten erläutert werden.

5. CurrencyUnit

CurrencyUnit modelliert die minimalen Eigenschaften einer Währung. Seine Instanzen können mit der MethodeMonetary.getCurrencyerhalten werden:

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

Wir erstellenCurrencyUnit unter Verwendung einerString Darstellung der Währung. Dies könnte zu einer Situation führen, in der wir versuchen, eine Währung mit nicht vorhandenem Code zu erstellen. Das Erstellen von Währungen mit nicht vorhandenen Codes löst eine Ausnahme vonUnknownCurrencyaus:

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

6. MonetaryAmount

MonetaryAmount ist eine numerische Darstellung eines Geldbetrags. Es ist immer mitCurrencyUnit verbunden und definiert eine monetäre Darstellung einer Währung.

Der Betrag kann auf verschiedene Arten implementiert werden, wobei der Schwerpunkt auf dem Verhalten der Anforderungen an die Währungsrepräsentation liegt, die von den einzelnen konkreten Anwendungsfällen definiert werden. Zum Beispiel. Money undFastMoney sind Implementierungen derMonetaryAmount-Schnittstelle.

FastMoney implementiertMonetaryAmount unter Verwendung vonlong als numerische Darstellung und ist auf Kosten der Präzision schneller alsBigDecimal; Es kann verwendet werden, wenn wir Leistung und Präzision brauchen, ist kein Problem.

Eine generische Instanz kann mithilfe einer Standardfactory erstellt werden. Lassen Sie uns die verschiedenen Methoden zum Abrufen vonMonetaryAmount-Instanzen zeigen:

@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

Wir können Geldarithmetik zwischenMoney undFastMoney durchführen, aber wir müssen vorsichtig sein, wenn wir Instanzen dieser beiden Klassen kombinieren.

Wenn wir zum Beispiel eine Euro-Instanz vonFastMoney mit einer Euro-Instanz vonMoney vergleichen, ist das Ergebnis, dass sie nicht gleich sind:

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

Wir können Additions-, Subtraktions-, Multiplikations-, Divisions- und andere monetäre arithmetische Operationen mit den von derMonetaryAmount-Klasse bereitgestellten Methoden ausführen.

Arithmetische Operationen sollten einArithmeticException auslösen, wenn die arithmetischen Operationen zwischen Beträgen die Fähigkeiten des verwendeten numerischen Darstellungstyps übertreffen. Wenn wir beispielsweise versuchen, eins durch drei zu teilen, erhalten wir einArithmeticException, weil das Ergebnis ist eine unendliche Zahl:

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

Beim Addieren oder Subtrahieren von Beträgen ist es besser, Parameter zu verwenden, die Instanzen vonMonetaryAmount sind, da wir sicherstellen müssen, dass beide Beträge dieselbe Währung haben, um Operationen zwischen Beträgen auszuführen.

7.1. Beträge berechnen

Eine Summe von Beträgen kann auf mehrere Arten berechnet werden. Eine Möglichkeit besteht einfach darin, die Beträge zu verketten mit:

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

Verkettung kann auch angewendet werden, um Folgendes zu subtrahieren:

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

Multiplizieren:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Oder teilen:

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Vergleichen wir unsere arithmetischen Ergebnisse mit Strings, da dies mit Strings übereinstimmt, da das Ergebnis auch die Währung enthält:

@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. Währungsrundung

Geldrundung ist nichts anderes als eine Umrechnung von einem Betrag mit unbestimmter Genauigkeit in einen gerundeten Betrag.

Für die Konvertierung verwenden wir die APIgetDefaultRounding, die von der KlasseMonetarybereitgestellt wird. Die standardmäßigen Rundungswerte werden von der Währung bereitgestellt:

@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. Währungsumrechnung

Währungsumrechnung ist ein wichtiger Aspekt im Umgang mit Geld. Leider weisen diese Konvertierungen eine Vielzahl unterschiedlicher Implementierungen und Anwendungsfälle auf.

Die API konzentriert sich auf die allgemeinen Aspekte der Währungsumrechnung basierend auf der Quell-, Zielwährung und dem Wechselkurs.

Währungsumrechnung oder Zugriff auf Wechselkurse können parametriert werden:

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

Eine Umrechnung ist immer an die Währung gebunden. MonetaryAmount kann einfach konvertiert werden, indem einCurrencyConversion an diewith-Methode des Betrags übergeben wird.

10. Währungsformatierung

Die Formatierung ermöglicht den Zugriff auf Formate basierend aufjava.util.Locale. Im Gegensatz zum JDK sind die von dieser API definierten Formatierer threadsicher:

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

Hier verwenden wir das vordefinierte Format und erstellen ein benutzerdefiniertes Format für unsere Währungen. Die Verwendung des Standardformats ist mit dem Methodenformat der KlasseMonetaryFormatsunkompliziert. Wir haben unser benutzerdefiniertes Format definiert und die Mustereigenschaft des Formatabfrage-Generators festgelegt.

Da die Währung im Ergebnis enthalten ist, testen wir unsere Ergebnisse nach wie vor mitStrings:

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

In diesem kurzen Artikel haben wir die Grundlagen des Java Money & Currency JSR behandelt.

Geldwerte werden überall verwendet, und Java beginnt, Geldwerte, Arithmetik oder Währungsumrechnung zu unterstützen und damit umzugehen.

Wie immer finden Sie den Code aus dem Artikelover on Github.