Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Operator < is undefined for argument Number, int

Tags:

java

generics

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();
  } 
like image 897
Richard Avatar asked Jul 30 '14 11:07

Richard


2 Answers

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... ;)

like image 79
isnot2bad Avatar answered Oct 08 '22 05:10

isnot2bad


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();
}
like image 34
Syam S Avatar answered Oct 08 '22 05:10

Syam S