Java Money und die Währungs-API

1. Übersicht

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. Setup

Definieren wir zunächst die Abhängigkeit in unserer Datei pom.xml :

 org.javamoney moneta 1.1  

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

3. JSR-354-Funktionen

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

  • Bereitstellung einer API für die Verarbeitung und Berechnung von Geldbeträgen
  • Definieren von Klassen, die Währungen und Geldbeträge darstellen, sowie monetäre Rundung
  • Um mit Wechselkursen umzugehen
  • Umgang mit der Formatierung und Analyse von Währungen und Geldbeträgen

4. Modell

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

Das Modell enthält zwei Hauptschnittstellen, CurrencyUnit und MonetaryAmount, die in den folgenden Abschnitten erläutert werden.

5. CurrencyUnit

CurrencyUnit modelliert die minimalen Eigenschaften einer Währung. Seine Instanzen können mit der Monetary.getCurrency- Methode abgerufen 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 erstellen CurrencyUnit mithilfe einer String- Darstellung der Währung. Dies kann dazu führen, dass wir versuchen, eine Währung mit nicht vorhandenem Code zu erstellen. Das Erstellen von Währungen mit nicht vorhandenen Codes löst eine UnknownCurrency- Ausnahme aus:

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

6. MonetaryAmount

MonetaryAmount ist eine numerische Darstellung eines Geldbetrags. Es ist immer mit CurrencyUnit verknüpft und definiert eine monetäre Darstellung einer Währung.

Der Betrag kann auf verschiedene Arten implementiert werden, wobei das Verhalten der Anforderungen an die monetäre Repräsentation im Mittelpunkt steht, die in den einzelnen konkreten Anwendungsfällen definiert sind. Zum Beispiel. Money und FastMoney sind Implementierungen der MonetaryAmount- Schnittstelle.

FastMoney implementiert MonetaryAmount unter Verwendung einer langen numerischen Darstellung und ist auf Kosten der Präzision schneller als BigDecimal . Es kann verwendet werden, wenn Leistung und Präzision erforderlich sind.

Eine generische Instanz kann mit einer Standardfactory erstellt werden. Lassen Sie uns die verschiedenen Methoden zum Abrufen von MonetaryAmount- 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 . Geldarithmetik

Wir können eine Geldarithmetik zwischen Money und FastMoney durchführen, aber wir müssen vorsichtig sein, wenn wir Instanzen dieser beiden Klassen kombinieren.

Wenn wir beispielsweise eine Euro-Instanz von FastMoney mit einer Euro-Instanz von Money 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"))); }

Mit den von der MonetaryAmount- Klasse bereitgestellten Methoden können wir Additions-, Subtraktions-, Multiplikations-, Divisions- und andere monetäre arithmetische Operationen ausführen .

Arithmetische Operationen sollten eine ArithmeticException 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 eine ArithmeticException, da das Ergebnis eine unendliche Zahl ist:

@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 von MonetaryAmount 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

Die Summe der Beträge kann auf verschiedene Arten berechnet werden. Eine Möglichkeit besteht darin, die Beträge einfach 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()); }

Die Verkettung kann auch zum Subtrahieren angewendet werden:

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

Multiplizieren:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Oder teilen:

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

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:

@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

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.

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.

10. Currency Formatting

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:

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

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

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

Geldwerte werden überall verwendet, und Java bietet an, Geldwerte, Arithmetik oder Währungsumrechnung zu unterstützen und zu verarbeiten.

Wie immer finden Sie den Code aus dem Artikel auf Github.