Java'da para için hangi veri türünü kullanmalısınız?
Java'da para için hangi veri türünü kullanmalısınız?
Yanıtlar:
Java, Currency
ISO 4217 para birimi kodlarını temsil eden bir sınıfa sahiptir .
BigDecimal
para birimi ondalık değerlerini temsil etmek için en iyi türdür.
Joda Money parayı temsil edecek bir kütüphane sağlamıştır.
Sen kullanabilirsiniz Para ve Döviz API (JSR 354) . Projenize uygun bağımlılıklar eklemeniz koşuluyla bu API'yı içinde kullanabilirsiniz.
Java 8 için, aşağıdaki başvuru uygulamasını bağımlılığınıza ekleyin pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Bu bağımlılık geçici javax.money:money-api
olarak bağımlılık olarak eklenecektir .
Daha sonra API'yı kullanabilirsiniz:
package com.example.money;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import java.util.Locale;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.money.MonetaryRounding;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;
import org.junit.Test;
public class MoneyTest {
@Test
public void testMoneyApi() {
MonetaryAmount eurAmount1 = Monetary.getDefaultAmountFactory().setNumber(1.1111).setCurrency("EUR").create();
MonetaryAmount eurAmount2 = Monetary.getDefaultAmountFactory().setNumber(1.1141).setCurrency("EUR").create();
MonetaryAmount eurAmount3 = eurAmount1.add(eurAmount2);
assertThat(eurAmount3.toString(), is("EUR 2.2252"));
MonetaryRounding defaultRounding = Monetary.getDefaultRounding();
MonetaryAmount eurAmount4 = eurAmount3.with(defaultRounding);
assertThat(eurAmount4.toString(), is("EUR 2.23"));
MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMAN);
assertThat(germanFormat.format(eurAmount4), is("EUR 2,23") );
}
}
Mümkün olan en küçük değeri temsil eden integral tip. Başka bir deyişle, programınız sent / dolar cinsinden değil sent cinsinden düşünmelidir.
Bu, GUI'nin dolar / avroya geri çevirmesini engellememelidir.
BigDecimal kullanılabilir, Float veya Double kullanmamanın iyi bir açıklaması burada görülebilir: Neden para birimini temsil etmek için Double veya Float kullanmıyorsunuz?
JSR 354: Para ve Para Birimi API'sı
JSR 354, Para ve Para Birimi ile kapsamlı hesaplamaları temsil etmek, taşımak ve gerçekleştirmek için bir API sağlar. Bu bağlantıdan indirebilirsiniz:
JSR 354: Para ve Para Birimi API'sı İndir
Şartname aşağıdaki şeylerden oluşur:
- Parasal tutarlar ve para birimlerini işlemek için bir API
- Değiştirilebilir uygulamaları destekleyen API'ler
- Uygulama sınıflarının örneklerini oluşturan fabrikalar
- Parasal tutarların hesaplanması, dönüştürülmesi ve biçimlendirilmesi için işlevsellik
- Java 9'a eklenmesi planlanan Para ve Para Birimleri ile çalışmak için Java API.
- Tüm spesifikasyon sınıfları ve arayüzler javax.money. * Paketinde bulunur.
JSR 354 Örneği: Para ve Para Birimi API'sı:
Bir MonetaryAmount oluşturma ve konsola yazdırma örneği şuna benzer:
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Referans uygulama API'sini kullanırken, gerekli kod çok daha basittir:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
API ayrıca MonetaryAmounts ile hesaplamaları da destekler:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
Para Birimi ve Parasal Miktar
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount, atanan para birimine, sayısal tutara, kesinliğine ve daha fazlasına erişmeyi sağlayan çeşitli yöntemlere sahiptir:
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'lar bir yuvarlama operatörü kullanılarak yuvarlanabilir:
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
MonetaryAmounts koleksiyonlarıyla çalışırken, filtreleme, sıralama ve gruplama için bazı güzel yardımcı yöntemler mevcuttur.
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"));
Özel Parasal Montaj işlemleri
// 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
Kaynaklar:
JSR 354 ile Java'da para ve para birimlerini işleme
Java 9 Para ve Para Birimi API'sini inceleme (JSR 354)
Ayrıca Bakınız: JSR 354 - Para Birimi ve Para
Parasal değerleri temsil etmek için BigDecimal kullanmalısınız.Çeşitli yuvarlama modlarını kullanmanıza izin verir ve finansal uygulamalarda yuvarlama modu genellikle yasalarca zorunlu tutulabilecek zor bir gereksinimdir.
Joda Money kullanırdım
Hala 0.6 sürümünde ama çok umut verici görünüyor
Moneta'yı (java para birimi JSR 354 uygulaması) BigDecimal ile performans açısından karşılaştırmak için bir mikrobenchmark (JMH) yaptım.
Şaşırtıcı bir şekilde, BigDecimal performansı moneta'nınkinden daha iyi görünüyor. Aşağıdaki moneta yapılandırma kullandım:
org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = HALF_UP
package com.despegar.bookedia.money;
import org.javamoney.moneta.FastMoney;
import org.javamoney.moneta.Money;
import org.openjdk.jmh.annotations.*;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
@Measurement(batchSize = 5000, iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 2)
@Threads(value = 1)
@Fork(value = 1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
public class BigDecimalBenchmark {
private static final Money MONEY_BASE = Money.of(1234567.3444, "EUR");
private static final Money MONEY_SUBSTRACT = Money.of(232323, "EUR");
private static final FastMoney FAST_MONEY_SUBSTRACT = FastMoney.of(232323, "EUR");
private static final FastMoney FAST_MONEY_BASE = FastMoney.of(1234567.3444, "EUR");
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
@Benchmark
public void bigdecimal_string() {
new BigDecimal("1234567.3444").subtract(new BigDecimal("232323")).multiply(new BigDecimal("3.4"), mc).divide(new BigDecimal("5.456"), mc);
}
@Benchmark
public void bigdecimal_valueOf() {
BigDecimal.valueOf(12345673444L, 4).subtract(BigDecimal.valueOf(232323L)).multiply(BigDecimal.valueOf(34, 1), mc).divide(BigDecimal.valueOf(5456, 3), mc);
}
@Benchmark
public void fastmoney() {
FastMoney.of(1234567.3444, "EUR").subtract(FastMoney.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money() {
Money.of(1234567.3444, "EUR").subtract(Money.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money_static(){
MONEY_BASE.subtract(MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
@Benchmark
public void fastmoney_static() {
FAST_MONEY_BASE.subtract(FAST_MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
}
Sonuçlanan
Benchmark Mode Cnt Score Error Units
BigDecimalBenchmark.bigdecimal_string thrpt 10 479.465 ± 26.821 ops/s
BigDecimalBenchmark.bigdecimal_valueOf thrpt 10 1066.754 ± 40.997 ops/s
BigDecimalBenchmark.fastmoney thrpt 10 83.917 ± 4.612 ops/s
BigDecimalBenchmark.fastmoney_static thrpt 10 504.676 ± 21.642 ops/s
BigDecimalBenchmark.money thrpt 10 59.897 ± 3.061 ops/s
BigDecimalBenchmark.money_static thrpt 10 184.767 ± 7.017 ops/s
Bir şey eksikse lütfen beni düzeltmekten çekinmeyin
Basit bir durumda (bir para birimi) için yeterli it'is Integer
/ Long
. Parayı sent (...) veya yüzde / bininci sentte tutun (sabit bölücü ile ihtiyacınız olan herhangi bir hassasiyet)
BigDecimal, para birimi için kullanılacak en iyi veri türüdür.
Para birimi için çok sayıda kap vardır, ancak hepsi temel veri türü olarak BigDecimal kullanır. Muhtemelen BigDecimal.ROUND_HALF_EVEN yuvarlama kullanarak BigDecimal ile yanlış gitmeyeceksiniz.
Önceki cevapların önerdiği gibi, bir çift, BigDecimal veya int sarmak Tiny Türleri kullanmayı seviyorum . (Hassas sorunlar ortaya çıkmadıkça bir çift kullanırım).
Tiny Type, çift parayı diğer çiftlerle karıştırmamak için size tip güvenliği sağlar.