4

Can anyone help me compare an Integer to a Double using generics?

This is what I have:

public static <T extends Comparable<? super T>> int compare(T arg1, T arg2)
{
    return arg1.compareTo(arg2);
}

public static void main(String[] args)
{   
    Number i = new Integer(5);
    Number j = new Double(7);

    System.out.println(GenericsTest.compare(i, j));
}

The error message I get is: Bound mismatch: The generic method compare(T, T) of type GenericsTest is not applicable for the arguments (Number, Number). The inferred type Number is not a valid substitute for the bounded parameter >

karakays
  • 3,643
  • 3
  • 20
  • 14
  • 1
    http://docs.oracle.com/javase/7/docs/api/java/lang/Number.html `Number ` does not extend `Comparable`? Could this be the reason for the exception? – Fildor Dec 07 '12 at 09:03
  • But I want to compare different types which have the same supertype. How can I proceed? – karakays Dec 07 '12 at 09:08
  • Then you cannot use `Number`. It does not meet your requirements for the supertype. You need the supertype to extend `Comparable` if you stick to your method. – Fildor Dec 07 '12 at 09:11
  • 1
    One side note, prefer using e.g. `Integer.valueOf(5)` instead of `new Integer(5)`, this way you avoid allocating memory unnecessarily. See Effective Java. – SkyWalker Dec 07 '12 at 10:45

5 Answers5

6

The idea of this solution is to widen to BigDecimal and then compare the two numbers (now is cleaner but somehow formatting doesn't work). Note you may reuse this static comparator without having to cast to double anywhere else. In the implementation you do need conversion to double not to lose information, basically you widen to the most general representation.

private static final Comparator<Number> NUMBER_COMPARATOR = new Comparator<Number>() {
    private BigDecimal createBigDecimal(Number value) {
        BigDecimal result = null;
        if (value instanceof Short) {
            result = BigDecimal.valueOf(value.shortValue());
        } else 
        if (value instanceof Long) {
            result = BigDecimal.valueOf(value.longValue());             
        } else 
        if (value instanceof Float) {
            result = BigDecimal.valueOf(value.floatValue());                                
        } else 
        if (value instanceof Double) {
            result = BigDecimal.valueOf(value.doubleValue());                               
        } else 
        if (value instanceof Integer) {
            result = BigDecimal.valueOf(value.intValue());                              
        } else {
            throw new IllegalArgumentException("unsupported Number subtype: " + value.getClass().getName());
        }
                       assert(result != null);

        return result;
    }

    public int compare(Number o1, Number o2) {
        return createBigDecimal(o1).compareTo(createBigDecimal(o2));
    };
};

public static void main(String[] args) {
    Number i = Integer.valueOf(5);
    Number j = Double.valueOf(7);
              // -1
    System.out.println(NUMBER_COMPARATOR.compare(i, j));

         i = Long.MAX_VALUE;
         j = Long.valueOf(7);
              // +1
         System.out.println(NUMBER_COMPARATOR.compare(i, j));

         i = Long.MAX_VALUE;
         j = Long.valueOf(-7);
              // +1
         System.out.println(NUMBER_COMPARATOR.compare(i, j));

         i = Long.MAX_VALUE;
         j = Double.MAX_VALUE;
              // -1
         System.out.println(NUMBER_COMPARATOR.compare(i, j));

    i = Long.MAX_VALUE;
    j = Long.valueOf(Long.MAX_VALUE - 1);
    // +1
    System.out.println(NUMBER_COMPARATOR.compare(i, j));

              // sorting Long values
    Long[] values = new Long[] {Long.valueOf(10), Long.valueOf(-1), Long.valueOf(4)};
    Arrays.sort(values, NUMBER_COMPARATOR);
              // [-1, 4, 10] 
    System.out.println(Arrays.toString(values));  
}
SkyWalker
  • 13,729
  • 18
  • 91
  • 187
  • 1
    `long` and `double` are both 64-bit types. They represent different ranges of numbers. You can't map `long` into `double` losslessly. I believe `Long.MAX_VALUE` and `Long.MAX_VALUE-1` will map into the same `double`, and therefore your `Comparator` wont be able to order them. Even if you compare both `longValue` and `doubleValue` that isn't going to cover other numbers, such as `BigInteger` and any third-party type. – Tom Hawtin - tackline Dec 07 '12 at 10:56
  • true, indeed, I edited the impl widening to the highest range possible BigDecimal. I also included a test for the border case you suggested, bulletproof now? – SkyWalker Dec 07 '12 at 11:11
1

As aready said in the comments, Number does not implement Comparable. But Double and Integer do.

One way to make this work is like this:

public static <T extends Comparable<? super T>> int compare(T arg1, T arg2)
{
    return arg1.compareTo(arg2);
}

public static void main(String[] args)
{   
    Double i = new Integer(5).doubleValue();
    Double j = new Double(7);

    System.out.println(GenericsTest.compare(i, j));
}
Frank
  • 16,476
  • 7
  • 38
  • 51
  • I think that my solution is better because you don't need to keep converting all the time you want to compare but it is done only once and encapsulated within the comparator itself. – SkyWalker Dec 07 '12 at 09:33
1

Number doesn't implement Comparable.

Declare both variables as Integer.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
0
private boolean compareObject(Object expected, Object actual) {
    if (expected instanceof Number && actual instanceof Number) {
        double e = ((Number) expected).doubleValue();
        double a = ((Number) actual).doubleValue();
        return e == a;
    } else {
        return com.google.common.base.Objects.equal(expected, actual);
    }
}
张焱伟
  • 61
  • 7
-1

Create a class that implements Comparable which takes a Number in the constructor. e.g.

public class GenericNumber implements Comparable<GenericNumber> {
    private Number num;
    public GenericNumber(Number num) {
        this.num = num;
    }
    // write compare function that compares num member of two
    // GenericNumber instances
}

Then simply do this:

GenericNumber i = new GenericNumber(new Integer(5));
GenericNumber j = new GenericNumber(new Double(7));
System.out.println(GenericsTest.compare(i,j));
RokL
  • 2,663
  • 3
  • 22
  • 26
  • 1) no need for a extraneous `GenericNumber`, that's what the supertype `Number` is already for 2) you don't define the meat of the solution basically how to generically compare 3) When you need to compare existing types you create a custom `Comparator` and not add a new type which is `Comparable`. Making a type Comparable is more suitable when you already have a type, and it has only one way (or default way) to compare. – SkyWalker Dec 07 '12 at 10:41
  • But supertype Number doesn't implement Comparable, which is why you need to make a wrapper. – RokL Dec 07 '12 at 10:48
  • No, you definitely dont need a wrapper. This is precisely the reason why you have the `Comparator` interface so you can add custom comparators to existing types and it beautifully integrates with the rest of the Java API e.g. Collections, Arrays, etc – SkyWalker Dec 07 '12 at 10:50
  • True, but the OP wanted to make a static function which doesn't use a Comparator. – RokL Dec 07 '12 at 10:51
  • Indeed, but the OP wasn't probably aware of all the possibilities. Separate from this you can have the same function accessing statically the NUMBER_COMPARATOR and keep the OP signature, really, no need for a GenericNumber that's what the Number is already for. – SkyWalker Dec 07 '12 at 10:54