web-dev-qa-db-fra.com

Ajouter BigDecimals à l'aide de Streams

J'ai une collection de BigDecimals (dans cet exemple, un LinkedList) que je voudrais ajouter ensemble. Est-il possible d'utiliser des flux pour cela?

J'ai remarqué que la classe Stream a plusieurs méthodes

Stream::mapToInt
Stream::mapToDouble
Stream::mapToLong

Chacune d'entre elles possède une méthode pratique sum(). Mais, comme nous le savons, l'arithmétique float et double est presque toujours une mauvaise idée.

Alors, y a-t-il un moyen pratique de résumer BigDecimals?

C'est le code que j'ai jusqu'à présent.

public static void main(String[] args) {
    LinkedList<BigDecimal> values = new LinkedList<>();
    values.add(BigDecimal.valueOf(.1));
    values.add(BigDecimal.valueOf(1.1));
    values.add(BigDecimal.valueOf(2.1));
    values.add(BigDecimal.valueOf(.1));

    // Classical Java approach
    BigDecimal sum = BigDecimal.ZERO;
    for(BigDecimal value : values) {
        System.out.println(value);
        sum = sum.add(value);
    }
    System.out.println("Sum = " + sum);

    // Java 8 approach
    values.forEach((value) -> System.out.println(value));
    System.out.println("Sum = " + values.stream().mapToDouble(BigDecimal::doubleValue).sum());
    System.out.println(values.stream().mapToDouble(BigDecimal::doubleValue).summaryStatistics().toString());
}

Comme vous pouvez le voir, je résume les BigDecimals en utilisant BigDecimal::doubleValue(), mais ce n'est (comme prévu) pas précis.

Modification post-réponse pour la postérité:

Les deux réponses ont été extrêmement utiles. Je voulais ajouter quelque chose: mon scénario réel ne comporte pas une collection de noms BigDecimals bruts, ils sont emballés dans une facture. Mais, j'ai pu modifier la réponse d'Aman Agnihotri afin de prendre en compte ce problème en utilisant la fonction map() pour le flux:

public static void main(String[] args) {

    LinkedList<Invoice> invoices = new LinkedList<>();
    invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
    invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
    invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
    invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));

    // Classical Java approach
    BigDecimal sum = BigDecimal.ZERO;
    for(Invoice invoice : invoices) {
        BigDecimal total = invoice.unit_price.multiply(invoice.quantity);
        System.out.println(total);
        sum = sum.add(total);
    }
    System.out.println("Sum = " + sum);

    // Java 8 approach
    invoices.forEach((invoice) -> System.out.println(invoice.total()));
    System.out.println("Sum = " + invoices.stream().map((x) -> x.total()).reduce((x, y) -> x.add(y)).get());
}

static class Invoice {
    String company;
    String invoice_number;
    BigDecimal unit_price;
    BigDecimal quantity;

    public Invoice() {
        unit_price = BigDecimal.ZERO;
        quantity = BigDecimal.ZERO;
    }

    public Invoice(String company, String invoice_number, BigDecimal unit_price, BigDecimal quantity) {
        this.company = company;
        this.invoice_number = invoice_number;
        this.unit_price = unit_price;
        this.quantity = quantity;
    }

    public BigDecimal total() {
        return unit_price.multiply(quantity);
    }

    public void setUnit_price(BigDecimal unit_price) {
        this.unit_price = unit_price;
    }

    public void setQuantity(BigDecimal quantity) {
        this.quantity = quantity;
    }

    public void setInvoice_number(String invoice_number) {
        this.invoice_number = invoice_number;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public BigDecimal getUnit_price() {
        return unit_price;
    }

    public BigDecimal getQuantity() {
        return quantity;
    }

    public String getInvoice_number() {
        return invoice_number;
    }

    public String getCompany() {
        return company;
    }
}
146
ryvantage

Réponse originale

Oui, c'est possible:

List<BigDecimal> bdList = new ArrayList<>();
//populate list
BigDecimal result = bdList.stream()
        .reduce(BigDecimal.ZERO, BigDecimal::add);

Qu'est-ce qu'il fait est:

  1. Obtenir un List<BigDecimal>.
  2. Transformez-le en un Stream<BigDecimal>
  3. Appelez la méthode réduire.

    3.1. Nous fournissons une valeur d'identité pour addition, à savoir BigDecimal.ZERO.

    3.2. Nous spécifions le BinaryOperator<BigDecimal>, qui ajoute deux BigDecimal, via une référence de méthode BigDecimal::add.

Réponse mise à jour, après édition

Je vois que vous avez ajouté de nouvelles données. Par conséquent, la nouvelle réponse devient:

List<Invoice> invoiceList = new ArrayList<>();
//populate
Function<Invoice, BigDecimal> totalMapper = invoice -> invoice.getUnit_price().multiply(invoice.getQuantity());
BigDecimal result = invoiceList.stream()
        .map(totalMapper)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

C'est essentiellement la même chose, sauf que j'ai ajouté une variable totalMapper, qui a une fonction de Invoice à BigDecimal et renvoie le prix total de cette facture.

Ensuite, j'obtiens un Stream<Invoice>, le mappe sur un Stream<BigDecimal>, puis le réduit à un BigDecimal.

Maintenant, à partir d'un point de conception OOP, je vous conseillerais d'utiliser également la méthode total() que vous avez déjà définie. Cela devient alors encore plus simple:

List<Invoice> invoiceList = new ArrayList<>();
//populate
BigDecimal result = invoiceList.stream()
        .map(Invoice::total)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

Ici, nous utilisons directement la référence de la méthode dans la méthode map.

292
skiwi

Ce message contient déjà une réponse cochée, mais celle-ci ne filtre pas les valeurs nulles. La réponse correcte devrait empêcher les valeurs NULL en utilisant la fonction Object :: nonNull comme prédicat.

BigDecimal result = invoiceList.stream()
    .map(Invoice::total)
    .filter(Objects::nonNull)
    .filter(i -> (i.getUnit_price() != null) && (i.getQuantity != null))
    .reduce(BigDecimal.ZERO, BigDecimal::add);

Cela empêche les valeurs nulles d'essayer d'être additionnées à mesure que nous réduisons.

7
Siraj

Vous pouvez résumer les valeurs d'un flux BigDecimal à l'aide d'un réutilisable Collector nommé summingUp:

BigDecimal sum = bigDecimalStream.collect(summingUp());

La Collector peut être implémentée comme ceci:

public static Collector<BigDecimal, ?, BigDecimal> summingUp() {
    return Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
}
5
Igor Akkerman

Utilisez cette approche pour additionner la liste de BigDecimal:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce((x, y) -> x.add(y)).get();

Cette approche mappe chaque BigDecimal en tant que BigDecimal uniquement et les réduit en les additionnant, qui est ensuite renvoyé à l'aide de la méthode get().

Voici un autre moyen simple de faire la même somme:

List<BigDecimal> values = ... // List of BigDecimal objects
BigDecimal sum = values.stream().reduce(BigDecimal::add).get();

Mise à jour

Si je devais écrire la classe et l'expression lambda dans la question modifiée, je l'aurais écrit comme suit:

import Java.math.BigDecimal;
import Java.util.LinkedList;

public class Demo
{
  public static void main(String[] args)
  {
    LinkedList<Invoice> invoices = new LinkedList<>();
    invoices.add(new Invoice("C1", "I-001", BigDecimal.valueOf(.1), BigDecimal.valueOf(10)));
    invoices.add(new Invoice("C2", "I-002", BigDecimal.valueOf(.7), BigDecimal.valueOf(13)));
    invoices.add(new Invoice("C3", "I-003", BigDecimal.valueOf(2.3), BigDecimal.valueOf(8)));
    invoices.add(new Invoice("C4", "I-004", BigDecimal.valueOf(1.2), BigDecimal.valueOf(7)));

    // Java 8 approach, using Method Reference for mapping purposes.
    invoices.stream().map(Invoice::total).forEach(System.out::println);
    System.out.println("Sum = " + invoices.stream().map(Invoice::total).reduce((x, y) -> x.add(y)).get());
  }

  // This is just my style of writing classes. Yours can differ.
  static class Invoice
  {
    private String company;
    private String number;
    private BigDecimal unitPrice;
    private BigDecimal quantity;

    public Invoice()
    {
      unitPrice = quantity = BigDecimal.ZERO;
    }

    public Invoice(String company, String number, BigDecimal unitPrice, BigDecimal quantity)
    {
      setCompany(company);
      setNumber(number);
      setUnitPrice(unitPrice);
      setQuantity(quantity);
    }

    public BigDecimal total()
    {
      return unitPrice.multiply(quantity);
    }

    public String getCompany()
    {
      return company;
    }

    public void setCompany(String company)
    {
      this.company = company;
    }

    public String getNumber()
    {
      return number;
    }

    public void setNumber(String number)
    {
      this.number = number;
    }

    public BigDecimal getUnitPrice()
    {
      return unitPrice;
    }

    public void setUnitPrice(BigDecimal unitPrice)
    {
      this.unitPrice = unitPrice;
    }

    public BigDecimal getQuantity()
    {
      return quantity;
    }

    public void setQuantity(BigDecimal quantity)
    {
      this.quantity = quantity;
    }
  }
}
5
Aman Agnihotri

Si une dépendance vis-à-vis de tiers ne vous gêne pas, il existe une classe nommée Collectors2 in Eclipse Collections qui contient des méthodes renvoyant des collecteurs pour addition et - résumé BigDecimal et BigInteger. Ces méthodes utilisent un paramètre Fonction pour vous permettre d'extraire une valeur BigDecimal ou BigInteger d'un objet.

List<BigDecimal> list = mList(
        BigDecimal.valueOf(0.1),
        BigDecimal.valueOf(1.1),
        BigDecimal.valueOf(2.1),
        BigDecimal.valueOf(0.1));

BigDecimal sum =
        list.stream().collect(Collectors2.summingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), sum);

BigDecimalSummaryStatistics statistics =
        list.stream().collect(Collectors2.summarizingBigDecimal(e -> e));
Assert.assertEquals(BigDecimal.valueOf(3.4), statistics.getSum());
Assert.assertEquals(BigDecimal.valueOf(0.1), statistics.getMin());
Assert.assertEquals(BigDecimal.valueOf(2.1), statistics.getMax());
Assert.assertEquals(BigDecimal.valueOf(0.85), statistics.getAverage());

Remarque: je suis un partisan des collections Eclipse.

4
Donald Raab