Quel est le meilleur type de données à utiliser pour de l'argent dans l'application Java?
Java a la classe Currency
qui représente les codes de devise ISO 4217. BigDecimal
est le meilleur type pour représenter les valeurs décimales en devise.
Joda Money a fourni une bibliothèque pour représenter l'argent.
Vous pouvez utiliser API Money and Currency (JSR 354). Vous pouvez utiliser cette API dans, à condition d'ajouter les dépendances appropriées à votre projet.
Pour Java 8, ajoutez l'implémentation de référence suivante en tant que dépendance de votre pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Cette dépendance ajoutera transitoirement javax.money:money-api
en tant que dépendance.
Vous pouvez ensuite utiliser l'API:
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") );
}
}
Un type intégral représentant la plus petite valeur possible. En d’autres termes, votre programme doit penser en centimes pas en dollars/euros.
Cela ne devrait pas vous empêcher d’avoir l’interface graphique à traduire en dollars/euros.
BigDecimal peut être utilisé, une bonne explication de pourquoi ne pas utiliser Float ou Double peut être vu ici: Pourquoi ne pas utiliser Double ou Float pour représenter la devise?
JSR 354: API Money and Currency
JSR 354 fournit une API permettant de représenter, de transporter et d'effectuer des calculs complets avec Money et Currency. Vous pouvez le télécharger à partir de ce lien:
JSR 354: Téléchargement de l’API monétaire et monétaire
La spécification comprend les éléments suivants:
- Une API pour gérer e. g. montants et devises
- API pour prendre en charge des implémentations interchangeables
- Fabriques pour créer des instances des classes d'implémentation
- Fonctionnalité pour les calculs, la conversion et la mise en forme des montants monétaires
- API Java permettant de travailler avec Money and Currency, qui devrait être incluse dans Java 9.
- Toutes les classes et interfaces de spécification sont situées dans le package javax.money. *.
Exemples de réalisation de l'API JSR 354: Monnaie et monnaie:
Voici un exemple de création d’un MonetaryAmount et de son impression sur la console:
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));
Lorsque vous utilisez l'API d'implémentation de référence, le code nécessaire est beaucoup plus simple:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
L'API prend également en charge les calculs avec MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit et MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount dispose de diverses méthodes permettant d’accéder à la devise affectée, au montant numérique, à sa précision, etc.:
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 peut être arrondi à l'aide d'un opérateur d'arrondi:
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
Lorsque vous travaillez avec des collections de MonetaryAmounts, certaines méthodes utilitaires de Nice pour le filtrage, le tri et le regroupement sont disponibles.
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"));
Opérations personnalisées MonetaryAmount
// 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
Ressources:
Traitement de l'argent et des devises dans Java avec JSR 354
En regardant dans la Java 9 API Money and Currency (JSR 354)
Voir aussi: JSR 354 - Monnaie et monnaie
J'ai effectué un microbenchmark (JMH) pour comparer Moneta (implémentation de la devise Java JSR 354) à BigDecimal en termes de performances.
Étonnamment, les performances BigDecimal semblent être meilleures que celles de moneta. J'ai utilisé la configuration suivante:
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);
}
}
Résultant en
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
S'il vous plaît, n'hésitez pas à me corriger si quelque chose me manque
Je voudrais utiliser Joda Money
Il est toujours à la version 0.6 mais semble très prometteur
Vous devez utiliser BigDecimal pour représenter les valeurs monétaires. Il vous permet d’utiliser une variété de modes d’arrondissement , ainsi que dans les applications financières, le mode d'arrondi est souvent une exigence difficile qui peut même être imposée par la loi.
Pour un cas simple (une devise), cela suffit Integer
/Long
. Gardez votre argent en cents (...) ou en centièmes/millièmes de cent (toute précision dont vous avez besoin avec un diviseur fixe)
BigDecimal est le meilleur type de données à utiliser pour la devise.
Il existe un grand nombre de conteneurs pour la devise, mais ils utilisent tous BigDecimal comme type de données sous-jacent. BigDecimal ne vous tracas pas, utilisez probablement l'arrondi BigDecimal.ROUND_HALF_EVEN.
J'aime utiliser Tiny Types qui encapsulerait soit un double, un BigDecimal ou un int comme les réponses précédentes l'ont suggéré. (J'utiliserais un double sauf si des problèmes de précision surgissent).
Un type minuscule vous donne la sécurité de type afin que vous ne confondiez pas un double argent avec d'autres doubles.