Java Money dan Currency API

1. Ikhtisar

JSR 354 - “Mata Uang dan Uang” membahas standardisasi mata uang dan jumlah moneter di Jawa.

Tujuannya adalah untuk menambahkan API yang fleksibel dan dapat diperluas ke ekosistem Java dan membuat bekerja dengan jumlah uang menjadi lebih sederhana dan lebih aman.

JSR tidak masuk ke JDK 9 tetapi merupakan kandidat untuk rilis JDK di masa mendatang.

2. Penyiapan

Pertama, mari tentukan ketergantungan ke dalam file pom.xml kita :

 org.javamoney moneta 1.1  

Versi terbaru dari ketergantungan tersebut dapat diperiksa di sini.

3. Fitur JSR-354

Sasaran API "Mata Uang dan Uang":

  • Untuk menyediakan API untuk menangani dan menghitung jumlah uang
  • Untuk menentukan kelas yang mewakili mata uang dan jumlah moneter, serta pembulatan moneter
  • Untuk menangani nilai tukar mata uang
  • Untuk menangani pemformatan dan penguraian mata uang dan jumlah moneter

4. Model

Kelas utama spesifikasi JSR-354, digambarkan dalam diagram berikut:

Model ini memiliki dua antarmuka utama, CurrencyUnit dan MonetaryAmount, yang dijelaskan di bagian berikut.

5. CurrencyUnit

CurrencyUnit memodelkan properti minimal suatu mata uang. Instansnya dapat diperoleh menggunakan metode 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); }

Kami membuat CurrencyUnit menggunakan representasi String dari mata uang tersebut, ini dapat menyebabkan situasi di mana kami mencoba membuat mata uang dengan kode yang tidak ada. Membuat mata uang dengan kode yang tidak ada memunculkan pengecualian UnknownCurrency :

@Test(expected = UnknownCurrencyException.class) public void givenCurrencyCode_whenNoExist_thanThrowsError() { Monetary.getCurrency("AAA"); } 

6. Jumlah Moneter

MonetaryAmount adalah representasi numerik dari suatu jumlah moneter. Itu selalu terkait dengan CurrencyUnit dan mendefinisikan representasi moneter dari suatu mata uang.

Jumlah tersebut dapat diimplementasikan dengan berbagai cara, dengan fokus pada perilaku persyaratan representasi moneter, yang ditentukan oleh setiap kasus penggunaan konkret. Sebagai contoh. Money dan FastMoney adalah implementasi dari antarmuka MonetaryAmount .

FastMoney mengimplementasikan MonetaryAmount menggunakan representasi numerik yang panjang , dan lebih cepat daripada BigDecimal dengan mengorbankan presisi; ini dapat digunakan ketika kita membutuhkan kinerja dan presisi tidak menjadi masalah.

Instance generik dapat dibuat menggunakan pabrik default. Mari kita tunjukkan cara berbeda untuk mendapatkan instance 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 . Aritmatika Moneter

Kita dapat melakukan aritmatika moneter antara Money dan FastMoney tetapi kita perlu berhati-hati saat menggabungkan contoh dari kedua kelas ini.

Misalnya, ketika kita membandingkan satu contoh Euro FastMoney dengan satu contoh Euro Uang , hasilnya adalah keduanya tidak sama:

@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"))); }

Kita dapat melakukan operasi penambahan, pengurangan, perkalian, pembagian, dan operasi aritmatika moneter lainnya menggunakan metode yang disediakan oleh kelas MonetaryAmount .

Operasi aritmatika harus menampilkan ArithmeticException , jika operasi aritmatika antara jumlah mengungguli kapabilitas tipe representasi numerik yang digunakan, misalnya, jika kita mencoba membagi satu dengan tiga, kita mendapatkan ArithmeticException karena hasilnya adalah bilangan tak hingga:

@Test(expected = ArithmeticException.class) public void givenAmount_whenDivided_thanThrowsException() { MonetaryAmount oneDolar = Monetary.getDefaultAmountFactory() .setCurrency("USD").setNumber(1).create(); oneDolar.divide(3); }

Saat menambah atau mengurangi jumlah, lebih baik menggunakan parameter yang merupakan instance MonetaryAmount , karena kita perlu memastikan bahwa kedua jumlah memiliki mata uang yang sama untuk melakukan operasi antar jumlah.

7.1. Menghitung Jumlah

Total jumlah dapat dihitung dengan berbagai cara, salah satunya adalah dengan merantai jumlahnya dengan:

@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()); }

Rantai juga dapat diterapkan untuk mengurangi:

Money calcAmtUSD = Money.of(1, "USD").subtract(fstAmtUSD); 

Mengalikan:

MonetaryAmount multiplyAmount = oneDolar.multiply(0.25);

Atau membagi:

MonetaryAmount divideAmount = oneDolar.divide(0.25);

Let's compare our arithmetic results using Strings, given that with Strings because the result also contains the currency:

@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. Monetary Rounding

Monetary rounding is nothing else than a conversion from an amount with an undetermined precision to a rounded amount.

We'll use the getDefaultRounding API provided by the Monetary class to make the conversion. The default rounding values are provided by the currency:

@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. Currency Conversion

Currency conversion is an important aspect of dealing with money. Unfortunately, these conversions have a great variety of different implementations and use cases.

The API focuses on the common aspects of currency conversion based on the source, target currency, and exchange rate.

Currency conversion or the access of exchange rates can be parametrized:

@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); }

A conversion is always bound to currency. MonetaryAmount can simply be converted by passing a CurrencyConversion to the amount’s with method.

10. Currency Formatting

The formatting allows the access of formats based on java.util.Locale. Contrary to the JDK, the formatters defined by this API are thread-safe:

@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); }

Here we're using the predefined format and creating a custom format for our currencies. The use of the standard format is straightforward using the method format of the MonetaryFormats class. We defined our custom format setting the pattern property of the format query builder.

As before because the currency is included in the result we test our results using Strings:

@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. Ringkasan

Dalam artikel singkat ini, kami telah membahas dasar-dasar JSR Uang & Mata Uang Java.

Nilai moneter digunakan di mana-mana, dan Java menyediakan mulai mendukung dan menangani nilai moneter, aritmatika, atau konversi mata uang.

Seperti biasa, Anda dapat menemukan kode dari artikel di Github.