dimo414 dimo414 - 1 year ago 68
Java Question

Stream reduction with wildcard types

I'm experimenting with

, and have run into a snag with the type system. Here's a toy example:

public static Number reduceNum1(List<Number> nums) {
return nums.stream().reduce(0, (a, b) -> a);

This works for any
, but what if I want to be able to reduce a list that
? extends Number
? This doesn't compile:

public static Number reduceNum2(List<? extends Number> nums) {
return nums.stream().reduce((Number)0, (a, b) -> a);

With the error:

ReduceTest.java:72: error: no suitable method found for reduce(Number,(a,b)->a)
return nums.stream().reduce((Number)0, (a, b) -> a);
method Stream.reduce(CAP#1,BinaryOperator<CAP#1>) is not applicable
(argument mismatch; Number cannot be converted to CAP#1)
method Stream.<U>reduce(U,BiFunction<U,? super CAP#1,U>,BinaryOperator<U>) is not applicable
(cannot infer type-variable(s) U
(actual and formal argument lists differ in length))
where U,T are type-variables:
U extends Object declared in method <U>reduce(U,BiFunction<U,? super T,U>,BinaryOperator<U>)
T extends Object declared in interface Stream
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output

I get, conceptually, why this happens.
must return something of the same type as the source's elements, and
? extends Number
isn't the same as
. But I'm unsure how to handle this case. How can I allow collections of a subclass (e.g.
) to be reduced (or collected)?

If it helps, here's a more practical example that similarly fails to compile:

public static <E> Set<E> reduceSet1(List<? extends Set<E>> sets) {
return sets.stream().reduce(ImmutableSet.<E>of(), (a, b) -> Sets.union(a, b));

Answer Source

The issue isn't actually with the ? extends, but with the identity parameter to reduce(). As Sotirios Delimanolis suggested, you can specify a bounded type N extends Number, but only if the identity value is null.

public static <N extends Number> N reduceNum3(List<N> nums) {
  return nums.stream().reduce(null, (a, b) -> a);

This is because both the wildcard and bounded methods cannot determine if the identity parameter will be the same type as the list's elements (unless it's null, which all types share).

The workaround is to use the three-argument reduce() method, which allows you to treat the result as a different type (even if it's not really).

Here's the Number example:

public static Number reduceNum4(List<? extends Number> nums) {
  return nums.stream().reduce((Number)0,
    (a, b) -> a,
    (a, b) -> a);

And here's the Set example:

public static <E> Set<E> reduceSet2(List<? extends Set<E>> sets) {
  return sets.stream().reduce((Set<E>)ImmutableSet.<E>of(),
    (a, b) -> Sets.union(a, b),
    (a, b) -> Sets.union(a, b));

Somewhat annoyingly you have to duplicate the reduction function since the accumulator and the combiner are different types. You presumably could define it once in a variable and pass it to both via an unsafe cast, but I'm not really certain that's an improvement.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download