58

I want the equivalent of this with a stream:

public static <T extends Number> T getSum(final Map<String, T> data) {
    T sum = 0;
    for (String key: data.keySet())
        sum += data.get(key);
    return sum;
}

This code doesn't actually compile because 0 cannot be assigned to type T, but you get the idea.

Jay
  • 9,314
  • 7
  • 33
  • 40
  • 1
    You simply cannot do this because Java doesn't accept operator overloading for classes. – Luiggi Mendoza May 06 '15 at 23:37
  • 1
    I don't get the idea. What do you want the answer to be, a `T` or a primitive type like `int`? – Paul Boddington May 06 '15 at 23:39
  • @pbabcdefp Integer, Double, or it could be int, or double – Jay May 06 '15 at 23:40
  • 2
    Also see http://stackoverflow.com/questions/3873215/can-i-do-arithmetic-operations-on-the-number-baseclass. If generic arithmetic is your goal you will basically need to find a library or write one. – Radiodef May 06 '15 at 23:48

5 Answers5

96

You can do this:

int sum = data.values().stream().mapToInt(Integer::parseInt).sum();
Suryavel TR
  • 3,576
  • 1
  • 22
  • 25
Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • Awesome! is there any way to make it generic? – Jay May 06 '15 at 23:44
  • 2
    @Jay You can pass the mapper and summer in. You'd end up with something like `MyMath.sum(map, Number::intValue, Integer::sum)` and the summer is a reduce operation. That's not much better so not really. – Radiodef May 06 '15 at 23:46
  • 1
    @Jay It's already generic in the sense that `T` is any type extending `Number`. If you mean, is there any way to make it give an `int` or a `double` etc, the answer is no as primitive types in java need to be dealt with separately. – Paul Boddington May 06 '15 at 23:46
  • @Jay 'Generic' means to decide what field of `Number` you want to sum up (`intValue`, `doubleValue`, `longValue`)? – isnot2bad May 06 '15 at 23:47
  • 3
    @Jay See this answer for how to do a reduction over a stream to get a generalized sum: http://stackoverflow.com/a/30019328/3973077 – Paul Boddington May 06 '15 at 23:50
  • @isnot2bad I wanted to wrap it in a function like T sum(Mapmap) {}, but this is pretty close – Jay May 06 '15 at 23:51
  • @Jay As others already stated: That's not possible. Can't be, as `Map` would be a legal argument, which could contain a mix of `Integer`, `Double`, `Long`... as values. – isnot2bad May 06 '15 at 23:53
  • 1
    @pbabcdefp good find! I guess I can do it if I create a class with an add method – Jay May 06 '15 at 23:53
  • 2
    What you'd have to do is make an interface `Summable` as in the question I link to and then make classes implement that interface for each number type (`WrappedInt`, `WrappedDouble` etc). – Paul Boddington May 06 '15 at 23:54
  • @pbabcdefp mapping to int is not a good idea for collection of Long or BigDecimal or Double, maybe correct your answer, as it's now too misleading – harshtuna May 07 '15 at 00:17
  • 1
    @harshtuna Most people don't bother to explain downvotes, so I appreciate that. I think the question was more about how to translate summing using a `for` loop into `Stream`s, and my answer does that. I agree a more generic approach, returning the same type as the numbers in the collection would be better, and I explain how to do that in the comments above. However the answer I give is not completely useless; for integral types `BigInteger`, `Integer`, `Long` etc it returns the sum modulo `2^32`. For `Float`, `Double` and `BigDecimal` I agree there isn't much point. – Paul Boddington May 07 '15 at 00:30
  • @pbabcdefp for-loop to streams is easy, that would be simply duplicate which does not deserve any answer. Generic Number handling is the challenge here. – harshtuna May 07 '15 at 00:50
  • 1
    @harshtuna But since we can't really do generic number handling we are suggesting alternatives. : ) It's fine if you want to downvote it I guess but we know we are suggesting alternatives. – Radiodef May 07 '15 at 00:55
  • @Radiodef 's [answer](https://stackoverflow.com/a/30089778/4770813) worked for me but this one shows ***incompatible types: invalid method reference \n incompatible types: integer cannot be converted to String \n The type of mapToInt(ToIntFunction super T>) is erroneous \n where T is a type-variable: \n T extends Object declared in interface Stream***. I'm just returning the whole line in a `public int` method. My map's key is a reference to one of my java classes and the value is an `Integer`. Please help, thanks – Scaramouche Apr 28 '19 at 01:23
37

Here's another way to do this:

int sum = data.values().stream().reduce(0, Integer::sum);

(For a sum to just int, however, Paul's answer does less boxing and unboxing.)

As for doing this generically, I don't think there's a way that's much more convenient.

We could do something like this:

static <T> T sum(Map<?, T> m, BinaryOperator<T> summer) {
    return m.values().stream().reduce(summer).get();
}

int sum = MyMath.sum(data, Integer::sum);

But you always end up passing the summer. reduce is also problematic because it returns Optional. The above sum method throws an exception for an empty map, but an empty sum should be 0. Of course, we could pass the 0 too:

static <T> T sum(Map<?, T> m, T identity, BinaryOperator<T> summer) {
    return m.values().stream().reduce(identity, summer);
}

int sum = MyMath.sum(data, 0, Integer::sum);
Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • 1
    there's also no generic solution to sum heterogeneous stream of Number (AtomicInteger, AtomicLong, BigDecimal, BigInteger, Byte, Double,...) and possibly custom types – harshtuna May 07 '15 at 00:38
  • The arguments for `Integer.sum` are both `int`, so in the first bit you're going to have to get from `T` to `int` somehow. I think the solution to the `Optional` problem is to make the method accept a zero value and use `orElse`. – Paul Boddington May 07 '15 at 00:44
  • 1
    @Radiodef Lambdas do unboxing, yes, but they won't unbox a `Number` to an `int`... – Paul Boddington May 07 '15 at 00:54
4
Integer sum = intMap.values().stream().mapToInt(d-> d).sum();

Get the HashMap.values method to Stream and collect mapToIn with lambda expression ending with aggregation sum method.

riddle_me_this
  • 8,575
  • 10
  • 55
  • 80
2

For Doubles you can use this simply.

Double sum = myMap.values().stream().mapToDouble(d -> d).sum();

works for me.

sachyy
  • 532
  • 4
  • 8
1

You can do it like this:

int creditAmountSum = result.stream().map(e -> e.getCreditAmount()).reduce(0, (x, y) -> x + y);
Saeed Zarinfam
  • 9,818
  • 7
  • 59
  • 72