user3529850
user3529850

Reputation: 986

Java stream. Sum two fields in a stream of objects

I have something like this:

Integer totalIncome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getIncome()).sum();
Integer totalOutcome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getOutcome()).sum();

How could I write that in one stream ? to collect f.e. Pair<Integer, Integer> with totalIncome and totalOutcome ?

EDITED:

Thank you guys for your comments, answers, and involvment. I would have a question about different approach to that problem using streams. What do you think about that:

final IncomeAndOutcome incomeAndOutcome = carDealer.getBrands()
                    .stream()
                    .map(Brand::getManufacturer)
                    .map(IncomeAndOutcome::of)
                    .reduce(IncomeAndOutcome.ZERO, IncomeAndOutcome::sum);

static class IncomeAndOutcome {

    private static final IncomeAndOutcome ZERO = of(0, 0);

    @Getter
    private final int income;

    @Getter
    private final int outcome;

    public static IncomeAndOutcome of(final int income, final int outcome) {
        return new IncomeAndOutcome(income, outcome);
    }

    public static IncomeAndOutcome of(final Manufacturer manufacturer) {
        return new IncomeAndOutcome(manufacturer.getIncome(), manufacturer.getOutcome());
    }

    IncomeAndOutcome(final int income, final int outcome) {
        this.income = income;
        this.outcome = outcome;
    }

    IncomeAndOutcome sum(final IncomeAndOutcome incomeAndOutcome) {
        return of(this.income + incomeAndOutcome.getIncome(), this.outcome + incomeAndOutcome.getOutcome());
    }
}

Upvotes: 8

Views: 6385

Answers (3)

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16276

By the way, OpenJDK 12 added this nice Collectors.teeing collector, which allows you to collect using two separate collectors and then combine their results, e.g.:

Pair<Integer, Integer> res =
    carDealer.getBrands().stream()
        .map(Brand::getManufacturer)
        .collect(
            Collectors.teeing(
                Collectors.summingInt(Manufacturer::getIncome),
                Collectors.summingInt(Manufacturer::getOutcome),
                Pair::of));

Upvotes: 2

Moksh Shah
Moksh Shah

Reputation: 21

This will give you total of income & outcome. Here 1st argument of reduce() is the identity. If you are not specifying that reduce() function will give optional value.

Pair<Integer, Integer> result = carDealer.getBrands()
                    .stream()
                    .map(brand -> Pair.of(brand.getManufacturer().getIncome(), brand.getManufacturer().getOutcome()))
                    .reduce(Pair.of(0, 0), (pair1, pair2) -> Pair.of(pair1.getFirst() + pair2.getFirst(), pair1.getSecond() + pair2.getSecond()));

Upvotes: 2

Eugene
Eugene

Reputation: 121088

Without measuring correctly - everything is guessing. The only argument I do agree with is about readability - this is hardly the case here; but in case you wanted to know this for academic purposes, you can do it:

int[] result = carDealer.getBrands()
         .stream()
         .map(brand -> new int[]{brand.getManufacturer().getIncome(),
                                 brand.getManufacturer().getOutcome()})
         .collect(Collector.of(
                    () -> new int[2],
                    (left, right) -> {
                        left[0] += right[0];
                        left[1] += right[1];
                    },
                    (left, right) -> {
                        left[0] += right[0];
                        left[1] += right[1];
                        return left;
                    }));

Upvotes: 2

Related Questions