JSR 354定义了一个用于处理货币和货币的新Java API,计划将其包含在Java 9中。在本文中,我们将研究参考实现的当前状态:
JavaMoney 。
就像我关于Java 8日期/时间API的帖子一样,该帖子将主要由显示新API的代码驱动。
但是在开始之前,我想引用一下规范中的一小部分,以总结一下此新API的动机:
货币价值是许多应用程序的关键功能,但是JDK提供的支持很少或没有。 现有的java.util.Currency类严格来说是一种用于表示当前ISO 4217货币的结构,但不是关联值或自定义货币。 JDK还不支持货币算术或货币转换,也不支持代表货币金额的标准值类型。
如果使用Maven,则可以通过将以下依赖项添加到项目中来轻松尝试参考实现的当前状态:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>0.9</version>
</dependency>
所有规范类和接口都位于javax.money。*包中。
我们将从两个核心接口CurrencyUnit和MonetaryAmount开始。 之后,我们将研究汇率,货币转换和格式。
货币单位和货币金额
CurrencyUnit为货币建模。 CurrencyUnit与现有的java.util.Currency类非常相似,不同之处在于它允许自定义实现。 根据规范,java.util.Currency应该可以实现CurrencyUnit。 可以使用MonetaryCurrencies工厂获取CurrencyUnit实例:
// getting CurrencyUnits by currency code
CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR");
CurrencyUnit usDollar = MonetaryCurrencies.getCurrency("USD");
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MontetaryAmount表示货币金额的具体数字表示。 MonetaryAmount始终绑定到CurrencyUnit。 与CurrencyUnit一样,MonetaryAmount是支持不同实现的接口。 CurrencyUnit和MonetaryAmount实现必须是不可变的,线程安全的,可序列化的和可比较的。
// 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和FastMoney是JavaMoney的两个MonetaryAmount实现。 Money是使用BigDecimal存储数字值的默认实现。 FastMoney是一种将金额存储在长字段中的替代实现。 根据文档,与Money相比,FastMoney的运行速度快10到15倍。 但是,FastMoney受long类型的大小和精度的限制。
请注意,Money和FastMoney是实现特定的类(位于org.javamoney.moneta。*而不是javax.money。*中)。 如果要避免实现特定的类,则必须获取MonetaryAmountFactory来创建MonetaryAmount实例:
MonetaryAmount specAmount = MonetaryAmounts.getDefaultAmountFactory()
.setNumber(123.45)
.setCurrency("USD")
.create();
如果实现类,货币单位和数值相等,则认为两个MontetaryAmount实例相等:
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具有多种方法,可用于访问分配的货币,数字量,其精度等:
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;
使用MonetaryAmounts
可以使用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);
四舍五入是使用金钱时的另一个重要部分。 可以使用舍入运算符舍入MonetaryAmount:
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
在这里,12.3456美元使用该货币的默认舍入取整。
当使用MonetaryAmounts的集合时,可以使用一些不错的实用程序方法进行过滤,排序和分组。 这些方法可以与Java 8 Stream API一起使用。
考虑以下集合:
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"));
现在,我们可以按CurrencyUnit过滤金额:
CurrencyUnit yen = MonetaryCurrencies.getCurrency("JPY");
CurrencyUnit dollar = MonetaryCurrencies.getCurrency("USD");
// filter by currency, get only dollars
// result is [USD 18, USD 7, USD 42]
List<MonetaryAmount> onlyDollar = amounts.stream()
.filter(MonetaryFunctions.isCurrency(dollar))
.collect(Collectors.toList());
// filter by currency, get only dollars and yen
// [USD 18, USD 7, JPY 13.37, USD 42]
List<MonetaryAmount> onlyDollarAndYen = amounts.stream()
.filter(MonetaryFunctions.isCurrency(dollar, yen))
.collect(Collectors.toList());
我们还可以过滤出小于或大于特定阈值的MonetaryAmounts:
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());
排序的工作方式类似:
// 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());
分组功能:
// Grouping by CurrencyUnit
// {USD=[USD 42, USD 7, USD 18], EUR=[EUR 2], JPY=[JPY 13.37]}
Map<CurrencyUnit, List<MonetaryAmount>> groupedByCurrency = amounts.stream()
.collect(MonetaryFunctions.groupByCurrencyUnit());
// Grouping by summarizing MonetaryAmounts
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还提供归约函数,可用于获取MonetaryAmount集合的最大值,最小值和总和:
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()); // "EUR 29.5"
自定义MonetaryAmount操作
MonetaryAmount提供了一个很好的扩展点,称为MonetaryOperator。 MonetaryOperator是一个功能接口,它将MonetaryAmount作为输入并根据输入创建一个新的MonetaryAmount。
// 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
一些标准API功能被实现为MonetaryOperator。 例如,我们在上面看到的舍入功能被实现为MonetaryOperator。
汇率
可以使用ExchangeRateProvider获得货币汇率。 JavaMoney带有多个不同的ExchangeRateProvider实现。 两个最重要的实现是ECBCurrentRateProvider和IMFRateProvider。
ECBCurrentRateProvider查询欧洲中央银行(ECB)数据供稿以获取当前汇率,而IMFRateProvider使用国际货币基金组织(IMF)转换率。
// 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");
如果未请求特定的ExchangeRateProvider,则将返回CompoundRateProvider。 CompoundRateProvider将汇率请求委托给一系列ExchangeRateProviders,并从第一个返回足够结果的提供程序返回结果。
// 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
货币转换
货币之间的转换是通过从ExchangeRateProviders获得的CurrencyConversions完成的:
// 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)
请注意,CurrencyConversion实现MonetaryOperator。 像其他运算符一样,可以使用MonetaryAmount.with()来应用它。
格式化和解析
MonetaryAmounts可以使用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);
请注意,¤符号(\ u00A)用作模式字符串内的货币占位符。
摘要
我们研究了新的Money and Currency API的许多部分。 该实现看起来已经很可靠了(但是肯定需要更多文档)。 我期待在Java 9中看到此API!
- 您可以在GitHub上找到此处显示的所有示例。
翻译自: https://www.javacodegeeks.com/2014/12/looking-into-the-java-9-money-and-currency-api-jsr-354.html