Search code examples
javalistfiltersumjava-stream

No suitable method found for collect/reduce - Can't sum a list of Integers with stream()


In the input, I have a list of Integers and Strings together.

The first stream works fine, the second stream works until the filter, I don't know why. My code:

import java.util.List;
import java.util.stream.*;

public class MixedSum {

    public int sum(List<?> mixed) {

        int m = mixed.stream().filter(x -> x instanceof String).map(x -> Integer.parseInt((String) x)).collect(Collectors.summingInt(Integer::intValue));
        int n = mixed.stream().filter(x -> x instanceof Integer).collect(Collectors.summingInt(Integer::intValue));

        return m + n;
    }
}

I have this error:

error: no suitable method found for collect(Collector<Integer,CAP#1,Integer>) int n = mixed.stream().filter(x -> x instanceof Integer).collect(Collectors.summingInt(Integer::intValue)); ^ method Stream.<R#1>collect(Supplier<R#1>,BiConsumer<R#1,? super CAP#2>,BiConsumer<R#1,R#1>) is not applicable (cannot infer type-variable(s) R#1 (actual and formal argument lists differ in length)) method Stream.<R#2,A>collect(Collector<? super CAP#2,A,R#2>) is not applicable (inferred type does not conform to upper bound(s) inferred: CAP#2 upper bound(s): Integer,Object) where R#1,T,R#2,A are type-variables: R#1 extends Object declared in method <R#1>collect(Supplier<R#1>,BiConsumer<R#1,? super T>,BiConsumer<R#1,R#1>) T extends Object declared in interface Stream R#2 extends Object declared in method <R#2,A>collect(Collector<? super T,A,R#2>) A extends Object declared in method <R#2,A>collect(Collector<? super T,A,R#2>) where CAP#1,CAP#2 are fresh type-variables: CAP#1 extends Object from capture of ? CAP#2 extends Object from capture of ? 1 error


Solution

  • Before applying the method reference Integer::intValue in the second stream you have to cast the elements of the stream to the Integer type.

    Since you want to get just a sum of integer primitives there is no need to resort to the help of collectors, instead, you can coerce a stream of objects to IntStream and apply the sum() method.

            int m = mixed.stream()
                    .filter(x -> x instanceof String)
                    .mapToInt(x -> Integer.parseInt((String) x))
                    .sum();
            
            int n = mixed.stream()
                    .filter(x -> x instanceof Integer)
                    .mapToInt(x -> (Integer) x)
                    .sum();
    

    There's no need to do iteration twice, and because in the source list you except only Integer and String types filter can be discarded. So your method eventually boils down to a couple of lines:

        public int sum(List<?> mixed) {
            return mixed.stream()
                    .mapToInt(x -> x instanceof Integer? (Integer) x : Integer.parseInt((String) x))
                    .sum();
        }
    

    Note:

    • Because Integer.parseInt() might cause NumberFormatException if conversion fails the stream will not produce the value if the source list contains at least one string that is comprised of other symbols apart from digits. That's what is called a fail-fast implementation and it makes sense if your intention was not to return a value in case of invalid data but to emphasize it with an exception.
    • Another thing worth to point out is that this code has value only as an exercise. In some languages, you might encounter union types (String | Number) and runtime checks using instanceof but Java cares about type-safety and in the real-life scenario you shouldn't create such a mixed collection in the first place. It was probably done using a row-type collection or Object class as a generic type in order to circumvent the compiler. Instead, it would be cleaner and less error-prone to process the source of string values separately like that:
       strSource.stream().map(Integer::parseInt).collect(Collectors.toList());
    

    As a consequence sum() method will be exempted from the redundant responsibility and contracts to:

       list.stream().mapToInt(Integer::intValue).sum();