Welchen Datentyp sollten Sie in Java für Geld verwenden?
Welchen Datentyp sollten Sie in Java für Geld verwenden?
Antworten:
Java hat eine Currency
Klasse, die die ISO 4217-Währungscodes darstellt.
BigDecimal
ist der beste Typ für die Darstellung von Währungsdezimalwerten.
Joda Money hat eine Bibliothek zur Darstellung von Geld bereitgestellt.
Sie können die Geld- und Währungs-API (JSR 354) verwenden . Sie können diese API in verwenden, sofern Sie Ihrem Projekt entsprechende Abhängigkeiten hinzufügen.
Fügen Sie für Java 8 die folgende Referenzimplementierung als Abhängigkeit zu Ihrer hinzu pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Diese Abhängigkeit wird transitiv javax.money:money-api
als Abhängigkeit hinzugefügt .
Sie können dann die API verwenden:
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") );
}
}
Ein ganzzahliger Typ, der den kleinstmöglichen Wert darstellt. Mit anderen Worten, Ihr Programm sollte in Cent und nicht in Dollar / Euro denken.
Dies sollte Sie nicht davon abhalten, die GUI wieder in Dollar / Euro umwandeln zu lassen.
BigDecimal kann verwendet werden. Eine gute Erklärung, warum Float oder Double nicht verwendet werden, finden Sie hier: Warum nicht Double oder Float zur Darstellung der Währung verwenden?
JSR 354: Geld- und Währungs-API
JSR 354 bietet eine API zum Darstellen, Transportieren und Durchführen umfassender Berechnungen mit Geld und Währung. Sie können es von diesem Link herunterladen:
JSR 354: Download der Geld- und Währungs-API
Die Spezifikation besteht aus folgenden Dingen:
- Eine API für den Umgang mit z. B. Geldbeträgen und Währungen
- APIs zur Unterstützung austauschbarer Implementierungen
- Fabriken zum Erstellen von Instanzen der Implementierungsklassen
- Funktionalität zur Berechnung, Umrechnung und Formatierung von Geldbeträgen
- Java-API für die Arbeit mit Geld und Währungen, die in Java 9 enthalten sein soll.
- Alle Spezifikationsklassen und Schnittstellen befinden sich im Paket javax.money. *.
Beispielbeispiele für JSR 354: Geld- und Währungs-API:
Ein Beispiel für das Erstellen eines MonetaryAmount und das Drucken auf der Konsole sieht folgendermaßen aus:
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));
Bei Verwendung der Referenzimplementierungs-API ist der erforderliche Code viel einfacher:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Die API unterstützt auch Berechnungen mit MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit und MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount verfügt über verschiedene Methoden, mit denen Sie auf die zugewiesene Währung, den numerischen Betrag, ihre Genauigkeit und vieles mehr zugreifen können:
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;
MonetaryAmounts können mit einem Rundungsoperator gerundet werden:
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
Bei der Arbeit mit Sammlungen von MonetaryAmounts stehen einige nützliche Dienstprogrammmethoden zum Filtern, Sortieren und Gruppieren zur Verfügung.
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"));
Benutzerdefinierte MonetaryAmount-Operationen
// 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
Ressourcen:
Umgang mit Geld und Währungen in Java mit JSR 354
Einblick in die Java 9 Money and Currency API (JSR 354)
Siehe auch: JSR 354 - Währung und Geld
Sie sollten BigDecimal verwenden , um Geldwerte darzustellen. Es ermöglicht Ihnen die Verwendung einer Vielzahl von Rundungsmodi. In Finanzanwendungen ist der Rundungsmodus häufig eine schwierige Anforderung, die möglicherweise sogar gesetzlich vorgeschrieben ist.
Ich würde Joda Money verwenden
Es ist immer noch in Version 0.6, sieht aber sehr vielversprechend aus
Ich habe ein Microbenchmark (JMH) erstellt, um Moneta (Implementierung von Java Currency JSR 354) mit BigDecimal hinsichtlich der Leistung zu vergleichen.
Überraschenderweise scheint die Leistung von BigDecimal besser zu sein als die von Moneta. Ich habe folgende Moneta-Konfiguration verwendet:
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);
}
}
Ergebend
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
Bitte zögern Sie nicht, mich zu korrigieren, wenn mir etwas fehlt
Für den einfachen Fall (eine Währung) it'is genug Integer
/ Long
. Halten Sie Geld in Cent (...) oder Hundertstel / Tausendstel Cent (jede Präzision, die Sie mit festem Teiler benötigen)
BigDecimal ist der beste Datentyp für die Währung.
Es gibt eine ganze Reihe von Währungscontainern, aber alle verwenden BigDecimal als zugrunde liegenden Datentyp. Mit BigDecimal werden Sie nichts falsch machen, wahrscheinlich mit BigDecimal.ROUND_HALF_EVEN-Rundung.
Ich benutze gerne kleine Typen die entweder ein Double, BigDecimal oder Int umschließen, wie in früheren Antworten vorgeschlagen. (Ich würde ein Double verwenden, wenn keine Präzisionsprobleme auftreten).
Ein kleiner Typ gibt Ihnen Sicherheit beim Schreiben, damit Sie ein doppeltes Geld nicht mit anderen doppelten verwechseln.