Quel type de données devriez-vous utiliser pour de l'argent en Java?
Quel type de données devriez-vous utiliser pour de l'argent en Java?
Réponses:
Java a une Currency
classe qui représente les codes de devise ISO 4217.
BigDecimal
est le meilleur type pour représenter les valeurs décimales des devises.
Joda Money a fourni une bibliothèque pour représenter l'argent.
Vous pouvez utiliser l' 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 à votre pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Cette dépendance s'ajoutera de javax.money:money-api
manière transitoire 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 devrait penser en centimes et non en dollars / euros.
Cela ne devrait pas vous empêcher de demander à l'interface graphique de le traduire en dollars / euros.
BigDecimal peut être utilisé, une bonne explication de pourquoi ne pas utiliser Float ou Double peut être vue ici: Pourquoi ne pas utiliser Double ou Float pour représenter la devise?
JSR 354: API Money and Currency
JSR 354 fournit une API pour représenter, transporter et 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 Money and Currency
La spécification comprend les éléments suivants:
- Une API pour gérer par exemple les montants monétaires et les devises
- API pour prendre en charge les implémentations interchangeables
- Usines de création d'instances des classes d'implémentation
- Fonctionnalité pour les calculs, la conversion et le formatage des montants monétaires
- API Java pour travailler avec Money et Devises, qui devrait être incluse dans Java 9.
- Toutes les classes de spécifications et interfaces se trouvent dans le package javax.money. *.
Exemples d'exemples de JSR 354: API Money and Currency:
Un exemple de création d'un MonetaryAmount et de son impression sur la console ressemble à ceci:
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));
Lors de l'utilisation de 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 différentes méthodes qui permettent d'accéder à la devise attribuée, au montant numérique, à sa précision et plus encore:
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, de belles méthodes utilitaires de filtrage, de tri et de 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 MonetaryAmount personnalisées
// 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:
Gestion de l'argent et des devises en Java avec JSR 354
Examen de l'API Java 9 Money and Currency (JSR 354)
Voir aussi: JSR 354 - Monnaie et argent
Vous devez utiliser BigDecimal pour représenter les valeurs monétaires. Il vous permet d'utiliser une variété de modes d'arrondi , et dans les applications financières, le mode d'arrondi est souvent une exigence stricte qui peut même être imposée par la loi.
j'utiliserais Joda Money
Il est toujours à la version 0.6 mais semble très prometteur
J'ai fait un microbenchmark (JMH) pour comparer Moneta (mise en œuvre de la devise java JSR 354) à BigDecimal en termes de performances.
Étonnamment, les performances de BigDecimal semblent meilleures que celles de moneta. J'ai utilisé la configuration moneta 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
N'hésitez pas à me corriger si quelque chose me manque
Pour un cas simple (une devise) c'est suffisant Integer
/ Long
. Gardez l'argent en cents (...) ou centième / millième de cents (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 de nombreux conteneurs pour la devise, mais ils utilisent tous BigDecimal comme type de données sous-jacent. Vous ne vous tromperez pas avec BigDecimal, probablement en utilisant l'arrondi BigDecimal.ROUND_HALF_EVEN.
J'aime utiliser les petits types qui encapsulerait un double, BigDecimal ou un int comme les réponses précédentes l'ont suggéré. (J'utiliserais un double à moins que des problèmes de précision ne surviennent).
Un type minuscule vous donne la sécurité de type afin que vous ne confondiez pas un double argent avec d'autres doubles.