การเพิ่ม BigDecimals โดยใช้ Streams


178

ฉันมีคอลเลกชันของ BigDecimals (ในตัวอย่างนี้กLinkedList) ที่ฉันต้องการเพิ่มเข้าด้วยกัน เป็นไปได้ไหมที่จะใช้สตรีมสำหรับสิ่งนี้

ฉันสังเกตเห็นว่าStreamห้องเรียนมีหลายวิธี

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

ซึ่งแต่ละsum()วิธีมีวิธีที่สะดวก แต่อย่างที่เรารู้floatและdoubleเลขคณิตเป็นความคิดที่เลว

ดังนั้นมีวิธีที่สะดวกในการสรุป BigDecimals หรือไม่

นี่คือรหัสที่ฉันมี

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

อย่างที่คุณเห็นฉันกำลังสรุป BigDecimals ที่ใช้BigDecimal::doubleValue()แต่นี่คือ (ตามที่คาดไว้) ไม่แม่นยำ

โพสต์คำตอบแก้ไขสำหรับลูกหลาน:

คำตอบทั้งสองมีประโยชน์อย่างมาก ฉันต้องการเพิ่มนิดหน่อย: สถานการณ์ในชีวิตจริงของฉันไม่เกี่ยวข้องกับการรวบรวมของดิบBigDecimalพวกเขาถูกห่อด้วยใบแจ้งหนี้ แต่ฉันสามารถแก้ไขคำตอบของ Aman Agnihotri สำหรับบัญชีนี้โดยใช้map()ฟังก์ชั่นสำหรับสตรีม:

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

คำตอบ:


354

คำตอบเดิม

ใช่มันเป็นไปได้:

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

มันคืออะไร:

  1. List<BigDecimal>ขอรับ
  2. เปลี่ยนเป็น Stream<BigDecimal>
  3. เรียกใช้วิธีการลด

    3.1 BigDecimal.ZEROเราจัดหาค่าตัวตนสำหรับการเพิ่มคือ

    3.2 เราระบุBinaryOperator<BigDecimal>ซึ่งจะเพิ่มสองBigDecimal's BigDecimal::addผ่านการอ้างอิงวิธี

อัปเดตคำตอบหลังจากแก้ไข

ฉันเห็นว่าคุณได้เพิ่มข้อมูลใหม่ดังนั้นคำตอบใหม่จะกลายเป็น:

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

ส่วนใหญ่จะเหมือนกันยกเว้นว่าฉันได้เพิ่มtotalMapperตัวแปรที่มีฟังก์ชันจากInvoiceถึงBigDecimalและส่งคืนราคารวมของใบแจ้งหนี้นั้น

แล้วฉันจะได้รับStream<Invoice>แผนที่ไปแล้วลดความมันไปStream<BigDecimal>BigDecimal

ตอนนี้จากจุดออกแบบ OOP ฉันอยากจะแนะนำให้คุณใช้total()วิธีการที่คุณได้กำหนดไว้แล้วจริง ๆ แล้วมันก็จะง่ายขึ้น:

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

ที่นี่เราใช้การอ้างอิงวิธีการในmapวิธีการโดยตรง


12
+1 สำหรับVSInvoice::total invoice -> invoice.total()
ryvantage

12
+1 สำหรับการอ้างอิงวิธีการและสำหรับการเพิ่มตัวแบ่งบรรทัดระหว่างการดำเนินการสตรีมซึ่ง IMHO ทั้งสองนี้ปรับปรุงการอ่านได้อย่างมาก
Stuart Marks

ว่ามันจะทำงานถ้าผมต้องการที่จะเพิ่มให้บอกว่าใบแจ้งหนี้ :: ทั้งหมดและใบแจ้งหนี้ :: ภาษีเป็นแถวใหม่
ริชาร์ด Lau

ไลบรารีมาตรฐาน Java มีฟังก์ชั่นสำหรับการรวมจำนวนเต็ม / คู่เช่นCollectors.summingInt()แต่คิดถึงพวกเขาสำหรับBigDecimals แทนที่จะเขียนreduce(blah blah blah)ที่อ่านยากจะเป็นการดีกว่าถ้าคุณเขียนตัวสะสมที่ขาดหายไปBigDecimalและมี.collect(summingBigDecimal())ที่ส่วนท้ายของไพพ์ไลน์ของคุณ
csharpfolk

2
วิธีนี้สามารถนำไปสู่ ​​NullponterException
gstackoverflow

11

โพสต์นี้มีคำตอบที่ตรวจสอบแล้ว แต่คำตอบไม่ได้กรองค่าว่าง คำตอบที่ถูกต้องควรป้องกันค่า Null โดยใช้ฟังก์ชัน Object :: nonNull เป็นเพรดิเคต

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

การทำเช่นนี้จะป้องกันไม่ให้มีค่าว่างจากการพยายามหาผลรวมเมื่อเราลดลง


7

คุณสามารถสรุปค่าของBigDecimalสตรีมโดยใช้ Collector ที่สามารถใช้ซ้ำได้ชื่อsummingUp:

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

Collectorสามารถดำเนินการเช่นนี้

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

5

ใช้วิธีนี้เพื่อรวมรายการ BigDecimal:

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

วิธีการนี้แมป BigDecimal แต่ละตัวเป็น BigDecimal เท่านั้นและลดพวกเขาโดยการรวมพวกเขาซึ่งจะถูกส่งกลับโดยใช้get()วิธีการ

นี่เป็นอีกวิธีง่ายๆในการทำข้อสรุปเดียวกัน:

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

ปรับปรุง

ถ้าฉันจะเขียนชั้นเรียนและแสดงออกแลมบ์ดาในคำถามที่แก้ไขแล้วฉันจะได้เขียนดังต่อไปนี้:

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)ประโยชน์ใช่ไหม ยังget()ไม่จำเป็น
Rohit Jain

@RohitJain: อัปเดต ขอบคุณ ฉันใช้get()มันเพราะมันจะส่งคืนค่าของOptionalการreduceโทรกลับ หากต้องการทำงานร่วมกับOptionalหรือเพียงพิมพ์ผลรวมใช่get()ไม่จำเป็น แต่การพิมพ์ตัวเลือกจะพิมพ์Optional[<Value>]ไวยากรณ์ตามโดยตรงซึ่งฉันสงสัยว่าผู้ใช้ต้องการ ดังนั้นเป็นสิ่งจำเป็นในทางที่จะได้รับค่าจากที่get() Optional
Aman Agnihotri

@ryvantage: ใช่วิธีการของคุณเป็นสิ่งที่ฉันจะทำมัน :)
Aman Agnihotri

อย่าใช้getสายที่ไม่มีเงื่อนไข! ถ้าvaluesเป็นรายการว่างตัวเลือกจะไม่มีค่าและจะโยนNoSuchElementExceptionเมื่อgetมีการเรียก คุณสามารถใช้values.stream().reduce(BigDecimal::add).orElse(BigDecimal.ZERO)แทน
eee

4

หากคุณไม่ทราบการพึ่งพาบุคคลที่สามที่มีชั้นเรียนชื่อCollectors2ในEclipse คอลเลกชันที่มีวิธีการสะสมกลับมาข้อสรุปและสรุป BigDecimal และ BigInteger วิธีการเหล่านี้ใช้ฟังก์ชั่นเป็นพารามิเตอร์เพื่อให้คุณสามารถแยกค่า BigDecimal หรือ BigInteger จากวัตถุ

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

หมายเหตุ: ฉันเป็นคอมมิชชันสำหรับ Eclipse Collections

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.