tags:

views:

491

answers:

5

I'd like to remove all "unchecked" warnings from this general utility method (part of a larger class with a number of similar methods). In a pinch, I can use @SuppressWarnings("unchecked") but I'm wondering if I can use generics properly to avoid the warning.

The method is intended to be allow callers to compare two objects by passing through to compareTo, with the exception that if the object is a strings it does it in a case insensitive manner.

public static int compareObject(Comparable o1, Comparable o2)
{
    if ((o1 instanceof String) && (o2 instanceof String))
        return ((String) o1).toUpperCase().compareTo(((String) o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

This was my first (incorrect) attempt at a solution. The parameters work fine, but the line o1.compareTo(o2) has a compile error "The method compareTo(capture#15-of ?) in the type Comparable is not applicable for the arguments (Comparable".

public static int compareObject(Comparable<?> o1, Comparable<?> o2)
{
    if ((o1 instanceof String) && (o2 instanceof String))
        return ((String) o1).toUpperCase().compareTo(((String) o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

Any suggestions?

A: 

Did you test the following?

public static <T> int compareObject(Comparable<T> o1, Comparable<T> o2)
{
    if ((o1 instanceof String) && (o2 instanceof String))
        return ((String) o1).toUpperCase().compareTo(((String) o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

I think it should work correctly.

jfpoilpret
I just checked, and it doesn't.
Michael Myers
+2  A: 

I just tried this:

public static <T extends Comparable> int compareObject(T o1, T o2) {
    if ((o1 instanceof String) && (o2 instanceof String))
        return ((String) o1).toUpperCase().compareTo(((String) o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

It compiles, but gives a unchecked cast warning on the compareTo() call.
I tried changing it to

public static <T extends Comparable<T>> int compareObject(T o1, T o2) {

and the String checks failed to compile ("inconvertible types: found: T, required: String"). I think this must be close, though.


EDIT: As pointed out in the comments, this is a bug in javac. The second form is indeed correct, but will not compile currently. Crazy as it may look, this is the code that works with no warnings:

public static <T extends Comparable<T>> int compareObject(T o1, T o2) {
    if (((Object) o1 instanceof String) && ((Object) o2 instanceof String))
        return ((String) (Object)o1).toUpperCase().compareTo(((String) (Object)o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

As you can see, the only difference is all the redundant casts to Object.

Michael Myers
I don't get that error with the latter signature. What version of javac are you using?
erickson
Java 6. I get red squigglies in NetBeans, and I just got the same error when I tried it from the command line.
Michael Myers
Hmm, Eclipse doesn't mind, but the JDK does, both version 5 and 6. I'd say it's a bug in the JDK.
erickson
Perhaps so... but it's not very useful to have code that *should* compile but doesn't.
Michael Myers
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6548436
erickson
Wow... I think that's the first time I've run into a javac bug. I'll update the answer to include this. Thanks, erickson!
Michael Myers
bizarre syntax but it works great!
Will Glass
+1  A: 

Here's what you are looking for:

public static <T extends Comparable<T>> int compareObject(T o1, T o2) {
 if ((o1 instanceof String) && (o2 instanceof String))
  return ((String) o1).toUpperCase().compareTo(((String) o2).toUpperCase());
 else
  return o1.compareTo(o2);
}
Frederic Morin
When the relevant bugs are fixed in javac, this is the signature to use.
erickson
confirmed-- works and compiles in Eclipse, doesn't compile in JDK. Bizarre.
Will Glass
+1  A: 

I hope you are aware that many of the approaches here do change the semantics of the method. With the original method you could compare objects of different types if they allow this, but with

public static <T extends Comparable<T>> int compareObject(T o1, T o2)

you cannot do this comparison anymore. A variant that allows this would be

    public static int compareObject2(Comparable<Object> o1, Comparable<Object> o2) {
    if (((Object) o1 instanceof String) && ((Object) o2 instanceof String))
        return ((String) (Object)o1).toUpperCase().compareTo(((String) (Object)o2).toUpperCase());
    else
        return o1.compareTo(o2);
}

(I inserted the workaround for the mentioned javac bug.) But this does not enhance type safety or anything, so in this case it is probably better to use the more understandable non generic method and live with a @SuppressWarnings("unchecked"). There is such a thing as overuse of generics.

hstoerr
thanks, good to note that. I'm actually ok with the new semantics of the answered solution, but it's helpful to see this.
Will Glass
A: 

What about the following:

  public static <T extends Comparable<T>> int compareObjects(T o1, T o2)
  {
    return o1.compareTo(o2);
  }

  public static int compareObjects(String o1, String o2)
  {
    return o1.compareToIgnoreCase(o2);
  }

The drawback is that when call compareObjects() with Objects that happen to be Strings, the compiler will bind your call to the first function, and you'll end up with the case-sensitive comparison.

Bartosz Klimek