I want to sum a list of Number
object and for each to use only the real value (if it's an Integer
I want to use only .intValue()
method and not .doubleValue
ex..) and I don't want to use instanceof
.
The return value needs to be of Number
type.
How can I do it with double dispatch or strategy pattern or something similar?
I can't extend each implementing class of Number
and I can't sum two Number
vars.
there are only 6 .xValue()
methods in Number
and I want to use each of them accordingly.
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. Eg
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, eg
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.
Just to summarize what was also discussed in the comments/chat.
Disclaimer(s): do not use this code in production or just be sure to understand what it really does. By no means is this solution the way to go or best practice. Returning a Number
whose type is adapted and a caller doesn't realize will give you troubles sooner or later. Please have a look at Holgers answer if you want to solve it in a caller-friendly way. This answer here just solves the issue of the OP the way he requested it. It hasn't any real benefit. It's basically here to just show what a bad idea it could be to solve it the way it was requested ;-). That having said, lets begin...
One way to define a strategy:
class Strategy {
Predicate<Number> predicate;
UnaryOperator<Number> transformation;
Strategy(Predicate<Number> predicate, UnaryOperator<Number> transformation) {
this.predicate = predicate;
this.transformation = transformation;
}
boolean applies(Number number) {
return predicate.test(number);
}
Number transformNumber(Number number) {
return transformation.apply(number);
}
}
A list of possible strategies could then look like
List<Strategy> strategies = Arrays.asList(
new Strategy(n -> n.byteValue() == n.doubleValue(), Number::byteValue),
new Strategy(n -> n.shortValue() == n.doubleValue(), Number::shortValue),
new Strategy(n -> n.intValue() == n.doubleValue(), Number::intValue),
new Strategy(n -> n.longValue() == n.doubleValue(), Number::longValue), // please read the disclaimer again...
new Strategy(n -> n.floatValue() == n.doubleValue(), Number::floatValue), // please spare your comments :-)
new Strategy(n -> true, Number::doubleValue) // ... lets continue!
);
A simple sum and the application of the strategies:
Optional<Number> sum(Number... numbers) {
return Arrays.stream(numbers)
.reduce(this::sumBasedOnStrategy);
}
Number sumBasedOnStrategy(Number one, Number two) {
Number result = one.doubleValue() + two.doubleValue();
return strategies.stream()
.filter(s -> s.applies(result))
.map(s -> s.transformNumber(result))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No known strategy for the given number"));
}
Now testing the sum strategies:
Stream.of(1, 256, 66000, 3000000000L, 1.1f, 3.4f, Double.MAX_VALUE)
.map(n -> sum(n, 1))
.map(Optional::get)
.map(Object::getClass)
.forEach(System.out::println);
What would you expect?
class java.lang.Byte
class java.lang.Short
class java.lang.Integer
class java.lang.Long
class java.lang.Double // really?
class java.lang.Float
class java.lang.Double
And here are the corresponding sum results...
2
257
66001
3000000001
2.100000023841858 // thank you for your attention ;-)
4.4
1.7976931348623157E308
Note that there are also other constellations that lead to incorrect results. And again: what does it help to get a Integer
after summing two double values? As Holger also showed in his comment (quoted):
A result value based selection would only work with a declared return type of
Number
, so the caller would not even notice the changing type, which would cause problems without a benefit. Think ofNumber n1 = 0.5 + 0.5, n2 = sum(0.5, 0.5);
wheren1.equals(n2)
would yieldfalse
as1
(Integer
) is not equal to1.0
(Double
).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.