tags:

views:

507

answers:

4

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)?

+3  A: 

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.

Steve B.
Thanks for your help! I'm sorry but I over-simplified my original example to the point where class-specific comparators would work. I've now updated the example to more-accurately reflect my requirement to gather delta records per property that has changed. You may additionally imagine that I would need to deep-scan a very large object graph to generate many such delta records.
Monkey Boson
+1  A: 

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.

notnoop
I'm trying to avoid doing exactly this, since it throws a warning: "Comparable is a raw type. References to generic type Comparable<T> should be parameterized". Now, I could just put a @SuppressWarnings at the top of my method to make this go away, but I was hoping for a "pure" answer. This solution avoids generics entirely.You're absolutely right about the cast() usage, though. It's exactly as fragile as the casting in your solution. The only reason it's there is because I was trying to do run-time type reflection / manipulation.
Monkey Boson
Given that generic is erasured at runtime, generic doesn't help you at all. I just modified the code to make the code more "generic". It's more important to make the code readable than to satisfy the compiler when it gives you no extra type-safety at all.
notnoop
Isn't it possible - at runtime - to introspect the type parameter (i.e. the "X" in Comparable<X>)? I thought you could do something like: comparable.getClass().getTypeParameters()[0].getGenericDeclaration(). Pretty? Definitely not. Given that all the code in this method was written in Java1.5, I thought it would be possible to write something that doesn't throw any warnings/errors, and is type-safe, without having to suppress anything. Is this a pipe dream?
Monkey Boson
+1  A: 
erickson
The problem here is that you don't know the Bean Property types until runtime. You cannot know what the type argument of `Comparable` in any convenient way.
notnoop
@erickson I completely agree with your analysis. However, shouldn't there be a way to achieve the correct casting - at run-time - so that I could test whether two objects are comparable against each other, and then perform that comparison? Given all the run-time methods that are available to us, it seems like you should be able to do this...
Monkey Boson
A: 

Well, given that I was not able to find a "pure" way to do this, and the fact that I kept running into corner cases (like, in addition to this one, the difficulty of handling properties that are collections), I decided to make my delta-generating method way dumber. I realized that I'm only testing 9 different types of objects, so I can just test which of the 9 objects I'm comparing, then cast to that object and do object-specific testing.

Implementing in this way took about an hour, and even though I have to re-compile every time any of the objects change, I figure I'm still in the black even if I spend days on this maintenance.

So, in the end, I guess the answer is that there is no answer. Java generics are implemented in such a way that it is impossible to avoid occasionally suppressing compiler warnings and risk run-time class cast exceptions.

Monkey Boson