1. Обзор
JSR 354 — «Валюта и деньги» касается стандартизации валют и денежных сумм в Java.
Его цель — добавить гибкий и расширяемый API в экосистему Java и сделать работу с денежными суммами проще и безопаснее.
JSR не попал в JDK 9, но является кандидатом для будущих выпусков JDK.
2. Настройка
Во-первых, давайте определим зависимость в нашем файле pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.1</version>
</dependency>
Последнюю версию зависимости можно проверить здесь .
3. Особенности JSR-354
Цели API «Валюта и деньги»:
- Предоставить API для обработки и расчета денежных сумм
- Чтобы определить классы, представляющие валюты и денежные суммы, а также денежное округление
- Для работы с курсами обмена валюты
- Для работы с форматированием и разбором валют и денежных сумм
4. Модель
Основные классы спецификации JSR-354 изображены на следующей диаграмме:
Модель содержит два основных интерфейса CurrencyUnit
и MonetaryAmount,
которые объясняются в следующих разделах.
5. Денежная единица
CurrencyUnit
моделирует минимальные свойства валюты. Его экземпляры можно получить с помощью метода Monetary.getCurrency
:
@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);
}
Мы создаем CurrencyUnit
, используя строковое
представление валюты, это может привести к ситуации, когда мы попытаемся создать валюту с несуществующим кодом. Создание валют с несуществующими кодами вызывает исключение UnknownCurrency
:
@Test(expected = UnknownCurrencyException.class)
public void givenCurrencyCode_whenNoExist_thanThrowsError() {
Monetary.getCurrency("AAA");
}
6. Денежная сумма
MonetaryAmount
— это числовое представление денежной суммы. Он всегда связан с CurrencyUnit
и определяет денежное представление валюты.
Сумма может быть реализована по-разному, ориентируясь на поведение требований денежного представления, определяемых каждым конкретным вариантом использования. Например. Деньги
и FastMoney
являются реализациями интерфейса MonetaryAmount .
FastMoney
реализует MonetaryAmount
, используя long
в качестве числового представления, и работает быстрее, чем BigDecimal
, за счет точности; его можно использовать, когда нам нужна производительность, а точность не является проблемой.
Общий экземпляр может быть создан с использованием фабрики по умолчанию. Давайте покажем другой способ получения экземпляров MonetaryAmount :
@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 . Денежная арифметика
Мы можем выполнять денежную арифметику между Money
и FastMoney
, но нам нужно быть осторожными, когда мы объединяем экземпляры этих двух классов.
Например, когда мы сравниваем один экземпляр FastMoney
в евро с одним экземпляром Money
в евро, получается, что они не совпадают:
@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")));
}
Мы можем выполнять сложение, вычитание, умножение, деление и другие денежные арифметические операции, используя методы, предоставляемые классом MonetaryAmount .
Арифметические операции должны вызывать ArithmeticException
, если арифметические операции между суммами превосходят возможности используемого числового типа представления, например, если мы пытаемся разделить один на три, мы получаем ArithmeticException
, потому что результатом является бесконечное число:
@Test(expected = ArithmeticException.class)
public void givenAmount_whenDivided_thanThrowsException() {
MonetaryAmount oneDolar = Monetary.getDefaultAmountFactory()
.setCurrency("USD").setNumber(1).create();
oneDolar.divide(3);
}
При добавлении или вычитании сумм лучше использовать параметры, которые являются экземплярами MonetaryAmount
, так как нам необходимо убедиться, что обе суммы имеют одинаковую валюту для выполнения операций между суммами.
7.1. Расчет сумм
Сумма сумм может быть рассчитана несколькими способами, один из которых состоит в том, чтобы просто объединить суммы в цепочку с помощью:
@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());
}
Цепочка также может быть применена к вычитанию:
Money calcAmtUSD = Money.of(1, "USD").subtract(fstAmtUSD);
Умножение:
MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);
Или деление:
MonetaryAmount divideAmount = oneDolar.divide(0.25);
Давайте сравним наши арифметические результаты с использованием строк, учитывая, что со строками, потому что результат также содержит валюту:
@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. Денежное округление
Денежное округление есть не что иное, как преобразование суммы с неопределенной точностью в округленную сумму.
Мы будем использовать API getDefaultRounding
, предоставляемый классом Monetary
, для преобразования. Значения округления по умолчанию предоставляются валютой:
@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. Конвертация валюты
Конвертация валюты является важным аспектом работы с деньгами. К сожалению, эти преобразования имеют большое разнообразие различных реализаций и вариантов использования.
API фокусируется на общих аспектах конвертации валюты на основе исходной, целевой валюты и обменного курса.
Конвертация валюты или доступ к обменным курсам могут быть параметризованы:
@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);
}
Конверсия всегда привязана к валюте. MonetaryAmount
можно просто преобразовать, передав метод CurrencyConversion
сумме with
.
10. Форматирование валюты
Форматирование позволяет получить доступ к форматам, основанным на java.util.Locale
. В отличие от JDK, средства форматирования, определенные этим API, являются потокобезопасными:
@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);
}
Здесь мы используем предопределенный формат и создаем собственный формат для наших валют. Использовать стандартный формат просто, используя формат метода класса MonetaryFormats
. Мы определили наш пользовательский формат, установив свойство шаблона построителя запросов формата.
Как и раньше, поскольку валюта включена в результат, мы проверяем наши результаты с помощью строк
:
@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. Резюме
В этой быстрой статье мы рассмотрели основы Java Money & Currency JSR.
Денежные значения используются повсеместно, и Java начинает поддерживать и обрабатывать денежные значения, арифметические операции или преобразование валюты.
Как всегда, вы можете найти код из статьи на Github .