JSR 354 – Ihre Nummer fürs Geld

„Eine Investition in Wissen bringt noch immer die besten Zinsen.“

Benjamin Franklin

Nicht jeder JSR schafft es direkt ins JDK und dann vergisst man schon einmal, das seit 2015 eine Referenzimplementierung für Java Money existiert. Wer mit Geldbeträgen und Währungen nicht mehr direkt auf BigDecimal arbeiten möchte, sollte sich deshalb unbedingt Moneta anschauen.

Moneta bietet die Möglichkeit mit Währungen zu arbeiten, mit Geldbeträge zu rechnen, das Runden von Geldbeträgen, die Währungsumrechnung nach Wechselkurs, Formatieren und Parsen von Geldbeträgen. Aber der Reihe nach.

Um mit Moneta arbeiten zu können, benötigt man die entsprechende Bibliothek im eigenen Projekt. Für Maven also einfach die folgende Dependency in die pom.xml einfügen.

<dependency>
  <groupId>org.javamoney</groupId>
  <artifactId>moneta</artifactId>
  <version>1.4.1</version>
  <type>pom</type>
</dependency>

Danach kann man mit Währungen und Beträgen arbeiten. Währungen werden über das Interface CurrencyUnit dargestellt. Eine CurrencyUnit kann über die Monetary Factory erzeugt werden.

CurrencyUnit eur = Monetary.getCurrency("EUR");
CurrencyUnit jpy = Monetary.getCurrency(Locale.JAPAN);
Collection<CurrencyUnit> all = Monetary.getCurrencies();

Im obigen Beispiel werden CurrencyUnit Instanzen über den ISO Code, dem Länder Locale oder über die Liste aller verfügbarer Währungen geholt. Wer eine nicht unterstützte Währung braucht, kann diese über den ServiceLoader Mechanismus mit Hilfe eines CurrencyProviderSpi erzeugen.

Geldbeträge werden mit dem Interface MonetaryAmount repräsentiert. Erzeugt werden auch diese über Factory-Methoden.

MonetaryAmount moneyEur = Monetary.getDefaultAmountFactory().setCurrency(eur).setNumber(19.99).create();
MonetaryAmount moneyJpy = Monetary.getDefaultAmountFactory().setCurrency("JPY").setNumber(19.99).create();

Sind die Geldbeträge erzeugt, dann kann mit ihnen gerechnet werden.

MonetaryAmount tenPercent= MonetaryOperators.percent(10).apply(Money.of(1.99, "EUR"));
MonetaryAmount amount = moneyEur.add(tenPercent);

Neben den hier dargestellten Methoden zur Berechnung eines prozentualen Betrages und der Addition von zwei Beträgen gibt es noch diverse andere Methoden. Da wären Promillebeträge, Subtraktion, Division und Multiplikation mit einer Zahl, Vergleichsmethoden und Kollektoren für die Stream-Verarbeitung.

MonetaryAmountFactory<Money> euroAmountFactory = Monetary.getDefaultAmountFactory().setCurrency("EUR");
MonetarySummaryStatistics statistic = IntStream.rangeClosed(1, 1000).mapToObj(x -> euroAmountFactory.setNumber(x).create())
    .collect(MonetaryFunctions.summarizingMonetary(eur));

Der hier dargestellte Sourcecode erstellt 1000 Beträge von 1€ bis 1000€ und sammelt die Ergebnisse in der statistic Variable. Der entsprechende Collector ähnelt dem ExtremaCollector aus dem Beitrag Extremes aus dem Stream, der auch mehrere Eigenschaften des Streams einsammelte. Über die Variable statistic sind dann Min- und Max-Wert (1€ bzw. 1000€), die Summe (500500€) und der Durchschnitt (500,50$) zugreifbar.

Die Summe entspricht übrigens der tausensten Dreieckzahl, die nach der Gaußformel \frac{n^2 + n}{2} berechnet werden kann.

Erfreulicherweise wird die Klasse MonetaryAmount in diversen Frameworks schon unterstützt. So können Attribute von diesem Type mit Java Bean Validation Annotationen geprüft werden.

@Positive
private MonetaryAmount price;

Diese @Positive Annotation prüft den übergebenen Preis auf einen positiven Wert. Negative Geldbeträge oder etwas umsonst gibt es nicht. Mit der @PositiveOrZero Annotation sind dann beispielsweise auch geschenkte Gäule gestattet.

Auch für die REST Schnittstelle gibt es Unterstützung in Form des jackson-datatype-money Moduls. In Spring Boot reicht die entsprechende Zalando Dependency.

<dependency>
  <groupId>org.zalando</groupId>
  <artifactId>jackson-datatype-money</artifactId>
  <version>1.3.0</version>
</dependency>

Danach noch etwas Code für die Konfiguration des ObjectMappers.

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
  return builder -> builder.postConfigurer(o -> o.registerModule(new MoneyModule().withDefaultFormatting()));
}

Danach liefert der REST Endpunkt für MonetaryAmount Attribute die folgende Ausgabe.

"price": {
  "amount": 9.95,
  "currency": "EUR",
  "formatted": "9,95 EUR"
}

Ohne den Aufruf von withDefaultFormatting würde in der Ausgabe das Attribut formatted fehlen.

Ein Highlight der Bibliothek darf in diesem Beitrag natürlich nicht fehlen. Mit Moneta können auch Währungsumrechnungen einfach und aktuell durchgeführt werden.

ExchangeRateProvider rateProvider = MonetaryConversions.getExchangeRateProvider("ECB");
ExchangeRate chfToUsdRate = rateProvider.getExchangeRate("CHF", "EUR");
System.out.println(chfToUsdRate.getFactor());

Der ExchangeRateProvider stellt die Daten für die Währungsumrechnungen bereit. Die im Beispiel angeforderte ExchangeRate für die Umrechnung von Schweizer Franken in Euro wird dabei direkt von Der EZB geholt und in der dritten Zeile als 1,014095933475307 ausgegeben.

Online Auskunft für den Wechselkurs Schweizer Franken zum Euro

Damit ist die Vorstellung der JSR 354 Implementierung Moneta auch schon wieder am Ende angekommen und jetzt viel Spaß beim Geld zählen!

Schreibe einen Kommentar