JSR 354 definiert eine neue Java-Währungs-API, deren offizielle Einführung in Java 9 geplant ist. In diesem Artikel werfen wir einen Blick auf die Referenzimplementierung: den aktuellen Fortschritt von JavaMoney.
Genau wie mein vorheriger Artikel über die neue Datums- und Uhrzeit-API von Java 8 demonstriert dieser Artikel hauptsächlich die Verwendung der neuen API durch Code.
Bevor ich anfange, möchte ich einen Absatz verwenden, um den Zweck dieses neuen Satzes von APIs, die durch die Spezifikation definiert werden, kurz zusammenzufassen:
Für viele Anwendungen ist der Geldwert ein Schlüsselmerkmal. aber JDK bietet dafür fast keine Unterstützung. Genau genommen stellt die vorhandene Klasse java.util.Currency nur eine Datenstruktur der aktuellen ISO 4217-Währung dar, verfügt jedoch über keine zugehörigen Werte oder benutzerdefinierten Währungen. JDK verfügt außerdem über keine integrierte Unterstützung für Währungsoperationen und -umrechnungen, geschweige denn über einen Standardtyp, der Währungswerte darstellen kann.
Wenn Sie Maven verwenden, müssen Sie dem Projekt nur die folgende Referenz hinzufügen, um die aktuellen Funktionen der Referenzimplementierung zu erleben:
<dependency> <groupId>org.javamoney</groupId> <artifactId>moneta</artifactId> <version>0.9</version> </dependency>
Die in der Spezifikation genannten Klassen und Schnittstellen sind unter dem Paket javax.money.*.
Beginnen wir mit den beiden Kernschnittstellen, CurrencyUnit und MonetaryAmount.
CurrencyUnit und MonetaryAmount
CurrencyUnit stellt die Währung dar. Sie ähnelt in gewisser Weise der aktuellen Klasse java.util.Currency, unterstützt jedoch benutzerdefinierte Implementierungen. Aus der Standarddefinition kann java.util.Currency diese Schnittstelle auch implementieren. Instanzen von „CurrencyUnit“ können über die MonetaryCurrencies-Fabrik abgerufen werden:
// 根据货币代码来获取货币单位 CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR"); CurrencyUnit usDollar = MonetaryCurrencies.getCurrency("USD"); // 根据国家及地区来获取货币单位 CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN); CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MontetaryAmount stellt den spezifischen Betrag einer bestimmten Währung dar. Normalerweise ist es an eine Währungseinheit gebunden.
MontetaryAmount ist wie auch CurrencyUnit eine Schnittstelle, die mehrere Implementierungen unterstützen kann.
Implementierungen von CurrencyUnit und MontetaryAmount müssen unveränderlich, threadsicher und vergleichbar sein.
/ get MonetaryAmount from CurrencyUnit CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR"); MonetaryAmount fiveEuro = Money.of(5, euro); // get MonetaryAmount from currency code MonetaryAmount tenUsDollar = Money.of(10, "USD"); // FastMoney is an alternative MonetaryAmount factory that focuses on performance MonetaryAmount sevenEuro = FastMoney.of(7, euro);
Money und FastMoney sind zwei Implementierungen von MonetaryAmount in der JavaMoney-Bibliothek. Money ist die Standardimplementierung, die BigDecimal zum Speichern von Beträgen verwendet. FastMoney ist eine optionale Implementierung, die zum Speichern von Beträgen den Typ „Long“ verwendet. Der Dokumentation zufolge sind Vorgänge auf FastMoney etwa 10 bis 15 Mal schneller als auf Money. Allerdings sind Betragsgröße und Präzision von FastMoney auf den Long-Typ beschränkt.
Beachten Sie, dass Money und FastMoney hier spezifische Implementierungsklassen sind (sie befinden sich unter dem Paket org.javamoney.moneta.*, nicht unter javax.money.*). Wenn Sie keinen bestimmten Typ angeben möchten, können Sie MonetaryAmountFactory verwenden, um eine Instanz von MonetaryAmount zu generieren:
MonetaryAmount specAmount = MonetaryAmounts.getDefaultAmountFactory() .setNumber(123.45) .setCurrency("USD") .create();
Diese beiden MontetaryAmount-Instanzen gelten nur dann und nur dann als gleich, wenn die Implementierungsklasse Währung ist Einheit und Wert sind alle gleich.
MonetaryAmount oneEuro = Money.of(1, MonetaryCurrencies.getCurrency("EUR")); boolean isEqual = oneEuro.equals(Money.of(1, "EUR")); // true boolean isEqualFast = oneEuro.equals(FastMoney.of(1, "EUR")); // false
MonetaryAmount enthält eine Fülle von Methoden, mit denen bestimmte Währungen, Beträge, Genauigkeiten usw. ermittelt werden können:
MonetaryAmount monetaryAmount = Money.of(123.45, euro); CurrencyUnit currency = monetaryAmount.getCurrency(); NumberValue numberValue = monetaryAmount.getNumber(); int intValue = numberValue.intValue(); // 123 double doubleValue = numberValue.doubleValue(); // 123.45 long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100 long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45 int precision = numberValue.getPrecision(); // 5 // NumberValue extends java.lang.Number. // So we assign numberValue to a variable of type Number Number number = numberValue;
Die Verwendung von MonetaryAmount
finden Sie unter Arithmetische Operationen auf MonetaryAmount:
MonetaryAmount twelveEuro = fiveEuro.add(sevenEuro); // "EUR 12" MonetaryAmount twoEuro = sevenEuro.subtract(fiveEuro); // "EUR 2" MonetaryAmount sevenPointFiveEuro = fiveEuro.multiply(1.5); // "EUR 7.5" // MonetaryAmount can have a negative NumberValue MonetaryAmount minusTwoEuro = fiveEuro.subtract(sevenEuro); // "EUR -2" // some useful utility methods boolean greaterThan = sevenEuro.isGreaterThan(fiveEuro); // true boolean positive = sevenEuro.isPositive(); // true boolean zero = sevenEuro.isZero(); // false // Note that MonetaryAmounts need to have the same CurrencyUnit to do mathematical operations // this fails with: javax.money.MonetaryException: Currency mismatch: EUR/USD fiveEuro.add(tenUsDollar);
Rundungsoperationen sind ein sehr wichtiger Teil der Betragsumrechnung. MonetaryAmount kann mit dem Rundungsoperator gerundet werden:
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD"); MonetaryAmount dollars = Money.of(12.34567, usd); MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd); MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
Hier werden 12,3456 US-Dollar gemäß den Standardrundungsregeln der aktuellen Währung umgerechnet.
Beim Betrieb der MonetaryAmount-Sammlung gibt es viele praktische Tools und Methoden, die zum Filtern, Sortieren und Gruppieren verwendet werden können. Diese Methoden können auch mit der Streams-API von Java 8 verwendet werden.
Sehen Sie sich die folgende Sammlung an:
List<MonetaryAmount> amounts = new ArrayList<>(); amounts.add(Money.of(2, "EUR")); amounts.add(Money.of(42, "USD")); amounts.add(Money.of(7, "USD")); amounts.add(Money.of(13.37, "JPY")); amounts.add(Money.of(18, "USD"));
Wir können den Betrag nach Währungseinheit filtern:
CurrencyUnit yen = MonetaryCurrencies.getCurrency("JPY"); CurrencyUnit dollar = MonetaryCurrencies.getCurrency("USD"); // 根据货币过滤,只返回美金 // result is [USD 18, USD 7, USD 42] List<MonetaryAmount> onlyDollar = amounts.stream() .filter(MonetaryFunctions.isCurrency(dollar)) .collect(Collectors.toList()); // 根据货币过滤,只返回美金和日元 // [USD 18, USD 7, JPY 13.37, USD 42] List<MonetaryAmount> onlyDollarAndYen = amounts.stream() .filter(MonetaryFunctions.isCurrency(dollar, yen)) .collect(Collectors.toList());
Wir können auch größere Beträge herausfiltern als oder kleiner als ein bestimmter Schwellenwert Der Betrag:
MonetaryAmount tenDollar = Money.of(10, dollar); // [USD 42, USD 18] List<MonetaryAmount> greaterThanTenDollar = amounts.stream() .filter(MonetaryFunctions.isCurrency(dollar)) .filter(MonetaryFunctions.isGreaterThan(tenDollar)) .collect(Collectors.toList());
Die Sortierung ist ebenfalls ähnlich:
// Sorting dollar values by number value // [USD 7, USD 18, USD 42] List<MonetaryAmount> sortedByAmount = onlyDollar.stream() .sorted(MonetaryFunctions.sortNumber()) .collect(Collectors.toList()); // Sorting by CurrencyUnit // [EUR 2, JPY 13.37, USD 42, USD 7, USD 18] List<MonetaryAmount> sortedByCurrencyUnit = amounts.stream() .sorted(MonetaryFunctions.sortCurrencyUnit()) .collect(Collectors.toList());
Es gibt auch Gruppierungsoperationen:
// 按货币单位进行分组 // {USD=[USD 42, USD 7, USD 18], EUR=[EUR 2], JPY=[JPY 13.37]} Map<CurrencyUnit, List<MonetaryAmount>> groupedByCurrency = amounts.stream() .collect(MonetaryFunctions.groupByCurrencyUnit()); // 分组并进行汇总 Map<CurrencyUnit, MonetarySummaryStatistics> summary = amounts.stream() .collect(MonetaryFunctions.groupBySummarizingMonetary()).get(); // get summary for CurrencyUnit USD MonetarySummaryStatistics dollarSummary = summary.get(dollar); MonetaryAmount average = dollarSummary.getAverage(); // "USD 22.333333333333333333.." MonetaryAmount min = dollarSummary.getMin(); // "USD 7" MonetaryAmount max = dollarSummary.getMax(); // "USD 42" MonetaryAmount sum = dollarSummary.getSum(); // "USD 67" long count = dollarSummary.getCount(); // 3
MonetaryFunctions Bietet auch eine Reduktionsfunktion, die verwendet werden kann, um den Maximalwert, den Minimalwert und die Summe zu erhalten:
List<MonetaryAmount> amounts = new ArrayList<>(); amounts.add(Money.of(10, "EUR")); amounts.add(Money.of(7.5, "EUR")); amounts.add(Money.of(12, "EUR")); Optional<MonetaryAmount> max = amounts.stream().reduce(MonetaryFunctions.max()); // "EUR 7.5" Optional<MonetaryAmount> min = amounts.stream().reduce(MonetaryFunctions.min()); // "EUR 12" Optional<MonetaryAmount> sum = amounts.stream().reduce(MonetaryFunctions.sum()); //
Benutzerdefinierte MonetaryAmount-Operation
MonetaryAmount bietet außerdem einen sehr benutzerfreundlichen Erweiterungspunkt namens MonetaryOperator . MonetaryOperator ist eine funktionale Schnittstelle, die einen MonetaryAmount-Eingabeparameter empfängt und ein neues MonetaryAmount-Objekt zurückgibt.
// A monetary operator that returns 10% of the input MonetaryAmount // Implemented using Java 8 Lambdas MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> { BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class); BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1")); return Money.of(tenPercent, amount.getCurrency()); }; MonetaryAmount dollars = Money.of(12.34567, "USD"); // apply tenPercentOperator to MonetaryAmount MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
Standard-API-Funktionen werden über die MonetaryOperator-Schnittstelle implementiert. Beispielsweise wird die oben beschriebene Rundungsoperation in Form der MonetaryOperator-Schnittstelle bereitgestellt.
Wechselkurs
Der Wechselkurs kann über ExchangeRateProvider abgerufen werden. JavaMoney wird mit mehreren verschiedenen ExchangeRateProvider-Implementierungen geliefert. Die beiden wichtigsten sind ECBCurrentRateProvider und IMFRateProvider.
ECBCurrentRateProvider fragt die Daten der Europäischen Zentralbank (EZB) ab und IMFRateProvider fragt den Wechselkurs des Internationalen Währungsfonds (IWF) ab.
// get the default ExchangeRateProvider (CompoundRateProvider) ExchangeRateProvider exchangeRateProvider = MonetaryConversions.getExchangeRateProvider(); // get the names of the default provider chain // [IDENT, ECB, IMF, ECB-HIST] List<String> defaultProviderChain = MonetaryConversions.getDefaultProviderChain(); // get a specific ExchangeRateProvider (here ECB) ExchangeRateProvider ecbExchangeRateProvider = MonetaryConversions.getExchangeRateProvider("ECB");
Wenn ExchangeRateProvider nicht angegeben ist, wird CompoundRateProvider zurückgegeben. Der CompoundRateProvider delegiert Wechselkursumrechnungsanfragen an eine Kette von ExchangeRateProvider und gibt Daten vom ersten Anbieter zurück, der genaue Ergebnisse zurückgibt.
// get the exchange rate from euro to us dollar ExchangeRate rate = exchangeRateProvider.getExchangeRate("EUR", "USD"); NumberValue factor = rate.getFactor(); // 1.2537 (at time writing) CurrencyUnit baseCurrency = rate.getBaseCurrency(); // EUR CurrencyUnit targetCurrency = rate.getCurrency(); // USD
Währungsumrechnung
Die Umrechnung zwischen verschiedenen Währungen kann über die von ExchangeRateProvider zurückgegebenen „CurrencyConversions“ durchgeführt werden.
// get the CurrencyConversion from the default provider chain CurrencyConversion dollarConversion = MonetaryConversions.getConversion("USD"); // get the CurrencyConversion from a specific provider CurrencyConversion ecbDollarConversion = ecbExchangeRateProvider.getCurrencyConversion("USD"); MonetaryAmount tenEuro = Money.of(10, "EUR"); // convert 10 euro to us dollar MonetaryAmount inDollar = tenEuro.with(dollarConversion); // "USD 12.537" (at the time writing)
Bitte beachten Sie, dass CurrencyConversion auch die MonetaryOperator-Schnittstelle implementiert. Wie andere Operationen kann es auch über die Methode MonetaryAmount.with() aufgerufen werden.
Formatieren und Parsen
MonetaryAmount可以通过MonetaryAmountFormat来与字符串进行解析/格式化。
// formatting by locale specific formats MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMANY); MonetaryAmountFormat usFormat = MonetaryFormats.getAmountFormat(Locale.CANADA); MonetaryAmount amount = Money.of(12345.67, "USD"); String usFormatted = usFormat.format(amount); // "USD12,345.67" String germanFormatted = germanFormat.format(amount); // 12.345,67 USD // A MonetaryAmountFormat can also be used to parse MonetaryAmounts from strings MonetaryAmount parsed = germanFormat.parse("12,4 USD");
可以通过AmountFormatQueryBuilder来生成自定义的格式。
// Creating a custom MonetaryAmountFormat MonetaryAmountFormat customFormat = MonetaryFormats.getAmountFormat( AmountFormatQueryBuilder.of(Locale.US) .set(CurrencyStyle.NAME) .set("pattern", "00,00,00,00.00 ¤") .build()); // results in "00,01,23,45.67 US Dollar" String formatted = customFormat.format(amount);
注意,这里的¤符号在模式串中是作为货币的占位符。