dimo414 - 1 year ago 49

Java Question

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

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

With the error:

I get, conceptually, why this happens. must return something of the same type as the source's elements, and 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 Number reduceNum1(List<Number> nums) {`

return nums.stream().reduce(0, (a, b) -> a);

}

This works for any

`List<Number>`

`? extends Number`

`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.

`Stream.reduce()`

`? extends Number`

`Number`

`List<Integer>`

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.