Since the actually returned type is relevant to the caller and has not much use for the caller when it still is non-obvious due to a declared type of Number
, it should be under the caller’s control and combined with a generic type signature, which allows the caller to actually use a particular return type. E.g.
public static <N extends Number, R extends Number> R sum(
List<? extends N> input, Function<? super N, ? extends R> cast,
BinaryOperator<R> addition) {
return input.stream().<R>map(cast).reduce(addition).orElse(null);
}
public static <N extends Number> N sum(
List<? extends N> input, BinaryOperator<N> addition) {
return sum(input, Function.identity(), addition);
}
This allows to request the calculation to be within the input types, e.g.
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Integer iSum1 = sum(list, Integer::sum);
Integer iSum2 = sum(list, Math::addExact);//throw on overflow
but also widening the type before summing up:
Long lSum = sum(list, Integer::longValue, Long::sum);
Likewise, you can handle Long
or Double
input types:
List<Long> list = Arrays.asList(1L, 2L, 3L, 4L);
Long lSum1 = sum(list, Long::sum);
Long lSum2 = sum(list, Math::addExact);//throw on overflow
// without precision loss:
BigInteger biSum = sum(list, BigInteger::valueOf, BigInteger::add);
List<Double> list = Arrays.asList(1.0, 2.0, 3.0, 4.0);
Double dSum = sum(list, Double::sum);
// without precision loss:
BigDecimal bdSum = sum(list, BigDecimal::valueOf, BigDecimal::add);
Or deal with mixed types:
List<Number> list = Arrays.asList(1, 2L, 3.0, 4F);
Double dSum = sum(list, Number::doubleValue, Double::sum);
BigDecimal bdSum = sum(list, n -> new BigDecimal(n.toString()), BigDecimal::add);
Note that Java’s Number
type hierarchy does not reflect the type conversion rules of the primitive types. So while a mixture of int
and long
values could be handled as long
whereas mixing int
and double
would require using double
to prevent loss of precision, there is no difference between mixing Integer
and Long
vs. mixing Integer
and Double
, both are just mixtures of different Number
subtypes. So in either case, you need a Number::xxxValue
conversion in-between and regardless of the actual combination, any Number::xxxValue
conversion would compile without a warning, even when it implies loss of precision.
Since large long
values could lose precision when being converted to double
, the last example use an intermediate String
value, to ensure that, in the presence of long
and double
input value, all conversions to BigDecimal
are lossless.