A quick call for help on generics.
Joda-Time has the following non-generic interface, with an example dummy implementation:
public interface ReadableInstant extends Comparable { ... int compareTo(Object readableInstant); ... } public class Dummy implements ReadableInstant { ... public int compareTo(Object readableInstant) { ... } ... }
I've been trying to generify this and maintain backwards binary and source compatibility without success. The following naive implementation works fine for new code:
public interface ReadableInstant extends Comparable<ReadableInstant> { ... int compareTo(ReadableInstant readableInstant); ... }
This doesn't work fine when the old code is combined (unaltered and without recompilation) with the new library.
Firstly, the old code is not source compatible.
Dummy
does not implement the altered method in the newly generified interface, thus the
old code doesn't compile without change.
Furthermore, it is not binary compatible.
Imagine a second class Foo
that implements the generified version of ReadableInstant
.
Now consider a class which loads Dummy
(unaltered and without recompilation)
by reflection as a ReadableInstant
and calls
compareTo
to compare. This will throw an AbstractMethodError
.
This is because Foo
expects there to be a method of the generified form, taking ReadableInstant
.
So, its basically a mess. I can't come up with a way to preserve backwards compatibility while adding generics. But I thought the whole point was to allow migration compatibility!
So, I thought that perhaps I'm missing something stupid. If so, please let me know! All the code is in svn (1.6 branch and TRUNK) for anyone wanting to play with real code.
(Firstly your generic parameters are missing from your code example - JRoller is treating them like HTML)
ReplyDeleteYou are correct about the lack of source compatibility. If classes that implement ReadableInstant are compiled against the new generified version, they will fail to compile and will need to be updated. This happened in the standard library when it was generified. e.g. http://www.velocityreviews.com/forums/t150999-stupid-calendar-class.html
I don't believe you are correct in the 2nd case. The compareTo method will always erase down to "int compareTo(Object)".
i.e. ReadableInstance.class.getMethod("compareTo", ReadableInstance.class) will always throw NoSuchMethodException, and ReadableInstance.class.getMethod("compareTo", Object.class) will work.
Have you looked at Java Generics and Collections by Maurice Naftalin and and Philip Wadler? Chapter 5 deals with all kinds of backward and forward compatibility issues related to generics.
ReplyDeleteThis is why projects like javolution ship both a 1.4 and 1.5+ version of their library.
ReplyDeleteHow about creating two compareTo methods:
ReplyDeleteint compareTo(ReadableInstant instant) {
// ...
}
int compareTo(Object instant) {
// call above
}
Don't you want something like:
ReplyDeletepublic interface ReadableInstant extends Comparable
Michael, I thought that something involving the & operator would solve the problem, but I don't want ReadableInstant to be parameterized. Any ReadableInstant can be compared to any other - its not exact subclass based, which your approach would result in.
ReplyDeleteI, too, suspect that there's no clean way out. Moreover, bending your code to allow backward compatibility might effect the Joda Time's readability, learnabilty etc. If comparing ReadableInstants only makes sense when doing it against other ReadableInstants, that's what the declaration should say. In the short run, migration might be hard, but in the long run you'll have a better library.
ReplyDeleteAs a migration path, you could provide an abstract class that implements the generified method, and wires it to the classic one. This will make migration easier (although not seamless) in some cases. You can @Deprecate it in the next-next release.
abstract class MigrationReadableInstant implements ReadableInstant {
@Override
public int compareTo(ReadableInstant r ) {
return compareTo( (Object)r );
}
abstract int compareTo( Object o );
}
The best solution I found was to remove the method definition from the ReadableInstant interface. This is compatible as it was inherited from the Comparable interface anyway. Having a real method of compareTo(ReadableInstant) was effectively adding a new method to the interface (as the erasure of Comaparable was compareTo(Object). Binary compatibility is now complete.
ReplyDeleteThere is also an AbstractInstant class. If projects have extended this class then they are unaffected by the change. Only projects that implemented ReadableInstant directly will have source incompatibility.