Как собрать несколько полей, используя Collectors в Java - PullRequest
6 голосов
/ 04 октября 2019

У меня есть собственный объект Itemized, в котором есть два поля: сумма и налог. У меня есть массив объектов Itemized, и я хочу суммировать два поля в одном потоке. Ниже описано, как я вычисляю сумму обоих полей.

double totalAmount = Arrays.stream(getCharges()).map(Itemized::getAmount).reduce(0.0, Double::sum));
double totalTax = Arrays.stream(getCharges()).map(Itemized::getTax).reduce(0.0, Double::sum));

Есть ли способ, которым мне не нужно анализировать поток два раза и можно суммировать два поля за один раз? Я не ищу суммы totalTax и totalAmount, но хочу получить их сумму отдельно. Я смотрел на коллекторы, но не смог найти ни одного примера, который позволил бы объединить несколько полей за один раз.

Ответы [ 6 ]

4 голосов
/ 04 октября 2019

использовать для цикла?

double taxSum = 0;
double amountSum = 0;
for (Itemized itemized : getCharges()) {
    taxSum += itemized.getTax();
    amountSum += itemized.getAmount();
}
1 голос
/ 04 октября 2019

Вы можете попробовать использовать Teeing Collector, например, так:

Arrays.stream(getCharges())                                // Get the charges as a stream
    .collect(Collectors                                    // collect
        .teeing(                                           // both of the following:
            Collectors.summingDouble(Itemized::getAmount), //     first, the amounts
            Collectors.summingDouble(Itemized::getTax),    //     second, the sums
            Map::entry                                     // and combine them as an Entry
        )
    );

Это должно дать вам Map.Entry<Double, Double> с суммой сумм в качестве ключа и суммой налога в качестве значения, котороеВы можете извлечь.

Редактировать:
Протестировал и скомпилировал - все работает. Вот и мы:

ItemizedTest.java

public class ItemizedTest
{
    static Itemized[] getCharges()
    {
        // sums should be first param = 30.6, second param = 75
        return new Itemized[] { new Itemized(10, 20), new Itemized(10.4,22), new Itemized(10.2, 33) };
    }

    public static void main(String[] args)
    {
        Map.Entry<Double, Double> sums = Arrays.stream(getCharges())
        .collect(Collectors
            .teeing(
                Collectors.summingDouble(Itemized::getAmount), 
                Collectors.summingDouble(Itemized::getTax),
                Map::entry
            )
        );
        System.out.println("sum of amounts: "+sums.getKey());
        System.out.println("sum of tax: "+sums.getValue());
    }
}

Itemized.java

public final class Itemized
{
    final double amount;
    final double tax;

    public double getAmount()
    {
        return amount;
    }

    public double getTax()
    {
        return tax;
    }

    public Itemized(double amount, double tax)
    {
        super();
        this.amount = amount;
        this.tax = tax;
    }
}

Вывод:

сумма сумм: 30,6
сумма налога: 75,0

PS: teeing Сборщик доступен только в Java 12 +.

0 голосов
/ 04 октября 2019

В вашем конкретном случае это можно сделать, используя ваш класс Itemized в качестве держателя значения.

Itemized result = Arrays.stream(getCharges())
    .reduce(new Itemized(), (acc, item) -> {
      acc.setAmount(acc.getAmount() + item.getAmount());
      acc.setTax(acc.getTax() + item.getTax());
      return acc;
    });
double totalAmount = result.getAmount();
double totalTax = result.getTax();
0 голосов
/ 04 октября 2019

Вы можете сделать это, используя Entry, но все же вы в конечном итоге создадите много объектов, лучшее решение, которое я бы предложил, это for loop, на который ответил NimChimpsky

Entry<Double, Double> entry = Arrays.stream(new Itemized[] {i1,i2})
          .map(it->new AbstractMap.SimpleEntry<>(it.getAmount(), it.getTax()))
          .reduce(new AbstractMap.SimpleEntry<>(0.0,0.0),
                  (a,b)->new AbstractMap.SimpleEntry<>(a.getKey()+b.getKey(),a.getValue()+b.getValue()));

    System.out.println("Amount : "+entry.getKey());
    System.out.println("Tax : "+entry.getValue());
0 голосов
/ 04 октября 2019

Со структурой данных, которая позволяет накопить обе суммы, вы можете уменьшить поток до одного объекта.

При этом используется double[] для удержания totalAmount в индексе 0 и totalTaxпо индексу 1 (другие опции включают SimpleEntry, Pair):

double[] res = Arrays.stream(getCharges())
                .map(ch -> new double[] { ch.getAmount(), ch.getTax() })
                .reduce(new double[] { 0, 0 }, 
                        (a1, a2) -> new double[] { a1[0] + a2[0], a1[1] + a2[1] });

double totalAmount = res[0], 
       totalTax = res[1];
0 голосов
/ 04 октября 2019

Вместо суммирования по полю вы определяете пользовательский объект для хранения значений суммы обоих полей:

ItemizedValues {
    private double amount;
    private double tax;

    public static final ItemizedValues EMPTY = new ItemizedValues(0, 0);

    // Constructor - getter - setter
    public static ItemizedValues of(Itemized item) {
         return new ItemizedValues(amount, tax);
    }

    public static ItemizedValues sum(ItemizedValues a, ItemizedValues b) {
         // Sum the fields respectively
         // It's your choice to reuse the existing instances, modify the values or create a brand new one
    }
}

Arrays.stream(getCharges())
      .map(ItemizedValues::of)
      .reduce(ItemizedValues.EMPTY, ItemizedValues::sum);
...