Am I trying to be too clever here?
private static <T extends Number> Long extractLong(T value) {
if ( value < Long.MIN_VALUE || value > Long.MAX_VALUE ) { // <= compile error
throw new NumberFormatException("Conversion from " + value + " to Long will overflow");
}
return value.longValue();
}
which yields the compile error:
The operator > is undefined for the argument type(s) T, long
But if I do the function explicitly it compiles :
private static Long extractLong(Long value) {
if ( value < Long.MIN_VALUE || value > Long.MAX_VALUE ) {
throw new NumberFormatException("Conversion from " + value + " to Long will overflow");
}
return value.longValue();
}
As Java does not support operator overloading, it is not possible to define the meaning of the comparison operators <
and >
for any type of objects. Or in other words:
Number a = new Long(1);
Number b = new Long(2);
if (a < b) // does NOT compile - objects cannot be compared using `<` or `>`!
The reason why your second example compiles is autoboxing. The Long
objects are automatically converted to long
values which are of course comparable as you did. But as there is no generic autoboxing for objects of type Number
, this does not work in the first case.
So how can we check for overflow then? The simplest way I think is to check the double
value first:
private static long extractLong(Number value) {
double v = value.doubleValue();
if (v < Long.MIN_VALUE || v > Long.MAX_VALUE) {
throw new NumberFormatException(...);
}
return value.longValue();
}
Note that this does not cover the case where value
is a very large BigDecimal
or BigInteger
that cannot be expressed as double
either. Therefore you'd need to do instanceof
checks too.
Warning: This test using doubleValue()
does not work when value
is slightly larger than Long.MAX_VALUE
(or slightly smaller than Long.MIN_VALUE
). The reason is that due to the nature of floating point values, conversion from integral or decimal numbers to double
is not exact. More concrete, all values from Long.MAX_VALUE - 511
to Long.MAX_VALUE + 1025
will be converted to one and the same double value (9.223372036854776E18
). Example:
double d1 = Long.MAX_VALUE - 511;
double d2 = Long.MAX_VALUE;
double d3 = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.valueOf(1025)).doubleValue();
// d1 == d2 == d3 due to lack of precision of double!
But this means, that the above expression v > Long.MAX_VALUE
will be false
for all those values although some of them are effectively larger than Long.MAX_VALUE
:
long l = extractLong(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
// l is now -9223372036854775808 => overflow check failed!
Side note: It is not necessary to use generics here! Just use Number
directly instead of T
and you can pass all kinds of numbers. Thanks to polymorphism, this works since Java 1.0... ;)
Basically the T here could be treated as an implementation of abstract class java.lang.Number
. And Number
does not support any of the arithemetic or logic operator like +, -, <, >, <=
etc. as Java does not support operator overloading. However the out-of-box implementation class like Integer, Long, Double
etc would work with these operators as they are being auto-boxed to int, long, double
etc. In your case when you say the type as T
the compiler cannot determine what implementation it would use until runtime. It can be an implementation which doesn't support auto-boxing. So compiler shown error.
But when you change the argument type to Long
the compiler know for sure that this could be auto-boxed an the operator could be applied. So no error. As a solution if you could use like
private static <T extends Number> Long extractLong(T value) {
if ( value.doubleValue() < Long.MIN_VALUE || value.doubleValue() > Long.MAX_VALUE ) {
throw new NumberFormatException("Conversion from " + value + " to Long will overflow");
}
return value.longValue();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With