Iterating over and reducing values of a Java map via Stream API

Viewed 62

Java 8 here. I have an array of Strings:

String[] animals = getsomehow(); // "dogs", "cats", "sheep", etc.

Then I have a map where the keys are Strings (specifically, the same literal values as some of the animals in the array above), and the values are a count (representing the number of those animals):

Map<String,Integer> animalCounts = new HashMap<String,Integer>();
animalCounts.put("sheep", 4);
animalCounts.put("dogs", 2);
animalCounts.put("cats", 0);
animalCounts.put("porcupines", null);
animalCounts.put("dolphins", 43);

I am trying to figure out how to use the Stream API to iterate over my animals array, and come up with a total number of animals. For instance, if my animals array had "sheep" and "dolphins" in it, then the total number of animals would be 4 + 43 = 47.

My best attempt so far:

int totalAnimals = Arrays.stream(animals)
    .reduce(
        0,
        (subtotal, animal) -> subtotal + animalCounts.get(animal));

However this yields a compiler error for the identity value of 0:

"Required type: String"

Can anyone spot where I'm going awry?

2 Answers

Can anyone spot where I'm going awry?

You're using the 2-argument version of reduce:

T reduce(T identity,
         BinaryOperator<T> accumulator)

As you can see, the identity value and the output has to be of the same type as the input, so it must be String.

Solution would be to use the 3-argument version of reduce:

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)

As an alternative, you can do it like this:

int totalAnimals = Arrays.stream(animals)
        .map(animalCounts::get)
        .filter(Objects::nonNull)
        .mapToInt(Integer::intValue)
        .sum();

The stream returned by Arrays.stream(animals) is of type Stream<String>, while you are reducing the stream with an identity of zero which is an int.

A simple way of doing this is mapping each animal to its count, and reduce the resulting Stream<Integer> to its sum:

int totalAnimals = Arrays.stream(animals)
                         .map(animalCounts::get)
                         .filter(Objects::nonNull)
                         .collect(Collectors.summingInt(Integer::intValue));

Note the use of getOrDefault(animal, 0) in the map, because the array may have elements not present in the map.

Related