Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting to a Comparable, then Comparing

Tags:

java

generics

I'm really trying to like generics, but so far the trouble they've caused outweighs any benefits. Please, please show me I'm wrong.

I understand the necessity of adding @SuppressWarnings("unchecked") when using generic-free frameworks (Spring, Hibernate). This alone really reduces generics' value, as does requiring classes be passed into the constructor to avoid the pitfalls of erasure. However, the real thorn always seems to be casting. I usually try for a while to get the syntax right, but then give up my attempt at purity, add a @SuppressWarnings, and move on with my life.

Here's an example: I'm reflecting over a bean to look for differences between two instances. Some properties implement Comparable such that (a.equals(b) == false) but (a.compareTo(b) == 0) (e.g. BigDecimal, Date). In these cases, I want the property to be considered the same.

MyObject original = getOriginal();
MyObject updated = getUpdated();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(MyObject.class)) {
    // Assume I'm putting in the try/catch block
    Object pOriginal = pd.getReadMethod().invoke(original, (Object[]) null);
    Object pUpdated = pd.getReadMethod().invoke(updated, (Object[]) null);

    boolean isPropertySame;

    if (Comparable.class.isAssignableFrom(pOriginal.getClass())) {
        // Type safety: The method compareTo(Object) belongs to the raw type Comparable. References to generic type Comparable<T> should be parameterized
        isPropertySame = Comparable.class.cast(pOriginal).compareTo(Comparable.class.cast(pUpdated)) == 0;

        // The method compareTo(capture#19-of ?) in the type Comparable<capture#19-of ?> is not applicable for the arguments (capture#21-of ? extends Comparable)
        Comparable<?> comparable = Comparable.class.cast(pOriginal);
        isPropertySame  = comparable.compareTo(comparable.getClass().getTypeParameters()[0].getGenericDeclaration().cast(pUpdated)) == 0;

        // Even if I get the generics right, I still get an error if pOriginal is java.sql.Timestamp and pUpdated is java.util.Date (happens all the time with Hibernate).
        isPropertySame = (help);

    } else {
        isPropertySame = pOriginal.equals(pUpdated);
    }

    if (!isPropertySame) {
        PropertyDelta delta = new PropertyDelta(pd, pOriginal, pUpdated);
        dao.save(delta);
    }
}

Any ideas on what I could put into (help)?

like image 822
Monkey Boson Avatar asked Aug 06 '09 20:08

Monkey Boson


People also ask

What is the difference between comparable and Comparator?

1) Comparable provides a single sorting sequence. In other words, we can sort the collection on the basis of a single element such as id, name, and price. The Comparator provides multiple sorting sequences. In other words, we can sort the collection on the basis of multiple elements such as id, name, and price etc.

How do you make something comparable?

To make an object comparable, the class must implement the Comparable interface. negative , if this object is less than the supplied object. zero , if this object is equal to the supplied object. positive , if this object is greater than the supplied object.

Where is Comparator and comparable used?

Comparable interface can be used to provide single way of sorting whereas Comparator interface is used to provide different ways of sorting. For using Comparable, Class needs to implement it whereas for using Comparator we don't need to make any change in the class.


3 Answers

This looks to me like going about it the hard way. You can either have your beans implement comparable, in which case you just compare them directly, or you create a comparator -

public class Bean implements Comparable<Bean> {...}

   public int compareTo(Bean other){ ... }
}

or

public int compare(Bean a, Bean b){ 
  Comparator<Bean> c = new Comparator<Bean>(){ 
    public int compareTo(Bean a, Bean b){ ... }
    public boolean equals(Object o){.. }
 };
   return c.compare(a, b);
}

I agree with you that java generics can get a bit, er... convoluted.

like image 143
Steve B. Avatar answered Sep 28 '22 11:09

Steve B.


I don't quite see what's wrong with just simply doing the following:

MyObject original = getOriginal();
MyObject updated = getUpdated();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(MyObject.class)) {
    // Assume I'm putting in the try/catch block
    Object pOriginal = pd.getReadMethod().invoke(original, (Object[]) null);
    Object pUpdated = pd.getReadMethod().invoke(updated, (Object[]) null);

    boolean isPropertySame;
    if (pOriginal instanceof Comparable) {
        @SuppressWarnings("unchecked")
        Comparable<Object> originalValue = (Comparable<Object>) pOriginal;
        @SuppressWarnings("unchecked")
        Comparable<Object> updatedValue = (Comparable<Object>) pUpdated;
        isPropertySame = originalValue.compareTo(updatedValue) == 0;
    } else {
        isPropertySame = pOriginal.equals(pUpdated);
    }

    if (!isPropertySame) {
        PropertyDelta delta = new PropertyDelta(pd, pOriginal, pUpdated);
        dao.save(delta);
    }
}

Using Class.cast in your case really don't help at all with type safety.

like image 41
notnoop Avatar answered Sep 28 '22 13:09

notnoop


It looks like the assumption is that if a class implements Comparable, the type parameter is the class itself. That is, "class X implements Comparable<X>". If that's the case, then it makes sense to say,

X a = new X(1), b = new X(2);
a.compareTo(b);

However, it is definitely possible to define a class like "class X implements Comparable<Y>". Then one could attempt something like this…

X a = new X(1), b = new X(2);
a.compareTo((Y) b);

… but clearly, a ClassCastException would be raised because b is not an instance of Y.

So, the warning is valid. The code, using raw types, is not type-safe, and could raise exceptions at runtime.

like image 32
erickson Avatar answered Sep 28 '22 12:09

erickson