1. Обзор
JSR 354 – “Валюта и деньги” касается стандартизации валют и денежных сумм на Java.
Его цель-добавить гибкий и расширяемый API в экосистему Java и сделать работу с денежными суммами проще и безопаснее.
JAR не попал в JDK 9, но является кандидатом на будущие выпуски JDK.
2. Настройка
Во-первых, давайте определим зависимость в вашем pom.xml файл:
org.javamoney moneta 1.1
Последнюю версию зависимости можно проверить здесь .
3. Особенности JSR-354
Цели API “Валюта и деньги”:
- Предоставить API для обработки и расчета денежных сумм
- Для определения классов, представляющих валюты и денежные суммы, а также денежное округление
- Для работы с курсами обмена валют
- Для работы с форматированием и синтаксическим анализом валют и денежных сумм
4. Модель
Основные классы спецификации JSR-354 показаны на следующей диаграмме:
Модель содержит два основных интерфейса Денежная единица и Денежная сумма, объясненные в следующих разделах.
5. Денежная Единица
Денежная единица моделирует минимальные свойства валюты. Его экземпляры можно получить с помощью метода 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); }
Мы создаем Единицу валюты , используя Строку представление валюты, это может привести к ситуации, когда мы попытаемся создать валюту с несуществующим кодом. Создание валют с несуществующими кодами вызывает Неизвестную валюту исключение:
@Test(expected = UnknownCurrencyException.class) public void givenCurrencyCode_whenNoExist_thanThrowsError() { Monetary.getCurrency("AAA"); }
6. Денежная Сумма
Денежная сумма – это числовое представление денежной суммы. Он всегда связан с Валютной единицей и определяет денежное представление валюты.
Сумма может быть реализована различными способами, ориентируясь на поведение требований к денежному представлению, определенных в каждом конкретном случае использования. Например. Деньги и Быстрые деньги являются реализациями интерфейса Денежная сумма .
Быстрые деньги реализует Денежную сумму используя длинное в качестве числового представления, и быстрее, чем BigDecimal за счет точности; его можно использовать, когда нам нужна производительность, а точность не является проблемой.
Универсальный экземпляр может быть создан с использованием фабрики по умолчанию. Давайте покажем другой способ получения Денежной суммы экземпляров:
@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 и Fast Money , но нам нужно быть осторожными, когда мы объединяем экземпляры этих двух классов.
Например, когда мы сравниваем один экземпляр евро Fast Money с одним экземпляром евро 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); }
При сложении или вычитании сумм лучше использовать параметры, которые являются экземплярами Денежная сумма , так как нам нужно убедиться, что обе суммы имеют одинаковую валюту для выполнения операций между суммами.
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 get округления по умолчанию , предоставляемый классом 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); }
Конвертация всегда привязана к валюте. Денежная сумма может быть просто конвертирована путем передачи Конвертации валюты в с методом суммы.
10. Форматирование валюты
Форматирование позволяет получить доступ к форматам, основанным на java.util.Локаль . В отличие от 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); }
Здесь мы используем предопределенный формат и создаем пользовательский формат для наших валют. Использование стандартного формата является простым с использованием формата метода класса Monetary Formats . Мы определили наш пользовательский формат, установив свойство pattern построителя запросов формата.
Как и прежде, поскольку валюта включена в результат, мы тестируем наши результаты с помощью Строк :
@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 provides начинает поддерживать и обрабатывать денежные значения, арифметику или конвертацию валют.
Как всегда, вы можете найти код из статьи на Github .