Akışları kullanarak BigDecimal'leri ekleme


178

Ben LinkedListbirlikte eklemek istiyorum BigDecimals (bu örnekte, a ) bir koleksiyon var . Bunun için akış kullanmak mümkün mü?

StreamSınıfın birkaç yöntemi olduğunu fark ettim

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

Her birinin uygun bir sum()yöntemi vardır. Ancak, bildiğimiz gibi floatve doublearitmetik neredeyse her zaman kötü bir fikirdir.

Yani, BigDecimal'leri özetlemenin uygun bir yolu var mı?

Bu şimdiye kadar sahip olduğum kod.

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());
}

Gördüğünüz gibi, BigDecimal'leri kullanarak özetliyorum BigDecimal::doubleValue(), ancak bu (beklendiği gibi) kesin değil.

Postity için cevap sonrası düzenleme:

Her iki cevap da son derece yardımcı oldu. Biraz eklemek istedim: Gerçek hayat senaryom ham koleksiyon içermiyor BigDecimal, bir faturaya sarılıyor. Ancak, map()akış için işlevi kullanarak Aman Agnihotri'nin cevabını bunun hesabını değiştirebildim :

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;
    }
}

Yanıtlar:


354

Orijinal cevap

Evet, bu mümkündür:

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

Yaptığı şey:

  1. Edinme a List<BigDecimal>.
  2. Dönüştürmek Stream<BigDecimal>
  3. Reduce yöntemini çağırın.

    3.1. Eklemek için bir kimlik değeri sağlıyoruz BigDecimal.ZERO.

    3.2. BinaryOperator<BigDecimal>İki tane ekleyen BigDecimal'i bir yöntem referansı ile belirtiriz BigDecimal::add.

Düzenlemeden sonra cevap güncellendi

Yeni veri eklediğinizi görüyorum, bu nedenle yeni cevap:

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);

Öyle bir eklemiş dışında çoğunlukla aynıdır totalMappergelen bir işlevi vardır değişken Invoiceiçin BigDecimalve bu faturanın toplam tutarı döndürür.

Sonra a alıyorum Stream<Invoice>, a ile eşleştiriyorum Stream<BigDecimal>ve sonra a BigDecimal.

Şimdi, bir OOP tasarım noktasından total(), daha önce tanımladığınız yöntemi de kullanmanızı tavsiye ederim , o zaman daha da kolaylaşır:

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

Burada doğrudan yöntemde yöntem başvurusunu kullanırız map.


12
Invoice::totalVs için +1 invoice -> invoice.total().
ryvantage

12
Yöntem referansları için ve her ikisi de IMHO'nun okunabilirliği önemli ölçüde geliştirdiği akış işlemleri arasında satır sonları eklemek için +1.
Stuart Marks,

i eklemek isteseydim nasıl çalışır? Diyelim Invoice :: total and Invoice :: tax yeni bir diziye
Richard Lau

Java standart kitaplığı zaten tamsayı / çiftleri toplamak için işlevlere sahiptir Collectors.summingInt(), ancak bunları BigDecimals için özlüyor . Bunun yerine yazı reduce(blah blah blah)için kollektörü eksik yazmak için daha iyi olurdu okumak zor olduğunu BigDecimalve var .collect(summingBigDecimal())senin boru hattının sonunda.
csharpfolk

2
Bu yaklaşım NullponterException yol açabilir
gstackoverflow

11

Bu gönderinin zaten kontrol edilmiş bir yanıtı var, ancak yanıt null değerleri filtrelemiyor. Doğru yanıt, yüklem olarak Object :: nonNull işlevini kullanarak null değerleri önlemelidir.

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

Bu, biz azaldıkça null değerlerin toplanmaya çalışmasını önler.


7

Bir değerlerini özetleyebilirim BigDecimalbir kullanarak akımına yeniden Collector adlı summingUp:

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

Bu Collectorşekilde uygulanabilir:

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

5

BigDecimal listesini toplamak için bu yaklaşımı kullanın:

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

Bu yaklaşım, her bir BigDecimal öğesini yalnızca bir BigDecimal olarak eşler ve bunları toplayarak azaltır, bu daha sonra get()yöntem kullanılarak döndürülür .

Aynı toplamayı yapmanın başka bir basit yolu:

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

Güncelleme

Düzenlenmiş soruda sınıf ve lambda ifadesini yazsaydım, şöyle yazardım:

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;
    }
  }
}

.map(n -> n)Orada işe yaramaz mı ? Ayrıca get()gerekli değildir.
Rohit Jain

@RohitJain: Güncellendi. Teşekkürler. Ben çağrı tarafından döndürülen get()değerini döndürür olarak kullanılır . Biri ile çalışmak istiyorsaOptionalreduceOptional toplamla veya sadece çıktıyı yazdırmak istiyorsa, evet, get()gerekli değildir. Ancak İsteğe Bağlı yazdırıldığında Optional[<Value>], kullanıcının ihtiyaç duyacağından şüphelendiğim sözdizimi doğrudan yazdırılır . Yani get()değer almak için bir şekilde gereklidir Optional.
Aman Agnihotri

@ryvantage: Evet, yaklaşımınız tam olarak bunu nasıl yapardım. :)
Aman Agnihotri

Koşulsuz bir getarama kullanmayın ! Eğervalues boş bir listedir opsiyonel hiçbir değerini içerir ve bir atacağım NoSuchElementExceptionzaman getdenir. Bunun values.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)yerine kullanabilirsiniz .
eee

4

Bir üçüncü taraf bağımlılığına aldırmazsanız , Eclipse Koleksiyonlarında , Toplayıcıları BigDecimal ve BigInteger'ı toplamak ve özetlemek için döndüren yöntemler içeren Collectors2 adında bir sınıf vardır . Bu yöntemler bir işlevi parametre olarak alır, böylece bir nesneden bir BigDecimal veya BigInteger değeri ayıklayabilirsiniz.

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());

Not: Eclipse Collections için bir komisyoncuyum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.