JSR 354는 Java 9에서 공식적으로 도입될 예정인 새로운 Java 통화 API를 정의합니다. 이 기사에서는 참조 구현인 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.* 패키지 아래에 있습니다.
두 가지 핵심 인터페이스인 MoneyUnit과 MonetaryAmount부터 시작해 보겠습니다.
CurrencyUnit 및 MonetaryAmount
CurrencyUnit은 통화를 나타냅니다. 이는 사용자 정의 구현을 지원한다는 점을 제외하면 현재 java.util.Currency 클래스와 다소 유사합니다. 표준 정의에서 java.util.Currency도 이 인터페이스를 구현할 수 있습니다. MoneyUnit의 인스턴스는 MonetaryCurrities 팩토리를 통해 얻을 수 있습니다.
// 根据货币代码来获取货币单位 CurrencyUnit euro = MonetaryCurrencies.getCurrency("EUR"); CurrencyUnit usDollar = MonetaryCurrencies.getCurrency("USD"); // 根据国家及地区来获取货币单位 CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN); CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MontetaryAmount는 특정 통화의 특정 금액을 나타냅니다. 일반적으로 이는 MoneyUnit에 바인딩됩니다.
CurrencyUnit과 마찬가지로 MontetaryAmount도 여러 구현을 지원할 수 있는 인터페이스입니다.
CurrencyUnit 및 MontetaryAmount의 구현은 변경이 불가능하고 스레드로부터 안전하며 비교 가능해야 합니다.
/ 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는 긴 유형을 사용하여 금액을 저장하는 선택적 구현입니다. 문서에 따르면 FastMoney의 작업은 Money의 작업보다 약 10~15배 빠릅니다. 그러나 FastMoney의 금액 크기와 정밀도는 Long 유형으로 제한됩니다.
여기서 Money 및 FastMoney는 특정 구현 클래스입니다(javax.money.*가 아닌 org.javamoney.moneta.* 패키지에 있음). 특정 유형을 지정하지 않으려는 경우 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;
MonetaryAmount 사용
는 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 미국 달러는 현재 통화의 기본 반올림 규칙에 따라 변환됩니다.
MonetaryAmount 컬렉션을 운영할 때 필터링, 정렬, 그룹화에 사용할 수 있는 실용적인 도구와 방법이 많이 있습니다. 이러한 메서드는 Java 8의 스트림 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"); // 根据货币过滤,只返回美金 // 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());
더 큰 금액을 필터링할 수도 있습니다. 특정 임계값 이하 금액:
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());
그룹화 작업도 있습니다:
// 按货币单位进行分组 // {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 최대값, 최소값 및 합계를 구하는 데 사용할 수 있는 축소 기능도 제공합니다.
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()); //
사용자 정의된 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)의 데이터를 쿼리하고 IFRateProvider는 국제통화기금(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는 환율 변환 요청을 ExchangeRateProvider 체인에 위임하고 정확한 결과를 반환하는 첫 번째 공급자로부터 데이터를 반환합니다.
// 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
통화 변환
ExchangeRateProvider에서 반환한 CurrentConversions를 통해 서로 다른 통화 간의 변환을 완료할 수 있습니다.
// 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() 메서드를 통해 호출할 수도 있습니다.
형식 지정 및 구문 분석
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);
注意,这里的¤符号在模式串中是作为货币的占位符。