Re: Mixing self conscious parametrized types with inheritance
Robert Klemme wrote:
Lew wrote:
I think the problem is that the base class is 'implements Comparable<S>'.
That really isn't the type assertion for 'Comparable'. The
self-referentiality is tangling the type assertions, for sure, but the real
problem is that you want the subclass to implement its own 'compareTo()'
separate from the superclass's. The wildcard dodge weakens the type
assertions to the point where you get away with it.
That's an unfriendly way to state it. :-) I rather like to think of
having found the proper generics solution for a given problem.
"Unfriendly"?
It's weaker to say "some subtype of Object" than "type Foo". It's not as
strong an assertion.
It's no unfriendlier than to say that 100 newtons is a weaker force than 1000.
It's also not a proper solution because of your dodge on the declaration of
'Comparable' implementation.
Normally a comparable class 'Foo' uses 'implements Comparable<Foo>'. You
didn't do that. If you had, you'd wind up with:
class BaseC<SC extends BaseC<?>> implements Comparable<BaseC<SC>> {
@Override
public int compareTo( BaseC<SC> o) {
return getKey() - o.getKey();
}
}
You cheated by having 'BaseC#compareTo()' take a different type argument from
what it's supposed to take. Yes, you did subvert the generics system by doing
that, but only by changing the semantics of what you're asserting.
I can't detect the cheating. I simply deferred specification of
The cheating is that you implement 'Comparable<SC>' ionstead of
'Comparable<Base<SC>>'. The non-cheating way is to implement 'Comparable' of
the class you want to compare, not the type parameter. Do you see that you
did not implement 'Comparable<Base<SC>>' but instead 'Comparable<SC>'? If you
see that, then you've detected the cheating.
Comparable's type parameter to the point in time when the type is
known. Since we cannot specify Comparable's type parameter in the
base class (because sub classes need different types here) we must
keep it undefined (i.e. need a type variable for it). That's
basically the general generics approach. It may be obfuscated a bit
through the fact that the type we want to use is always the class we
are actually writing but it's nevertheless the generics mantra of "if
you don't know the type yet, use a type parameter".
Then you'd have
Not "would" but "do": https://gist.github.com/868085#file_self_consciousness_test.java
Sorry, but that is just untrue. In there you have
class BaseC<SC extends BaseC<?>> implements Comparable<SC> {
I just now copied-and-pasted that line into this post, so I'm telling the truth.
class SubC<SC extends SubC<?>> extends BaseC<SC> {
@Override
public int compareTo( SubC<SC> o) {
final int cmp = getK() % 3 - o.getK() % 3;
return cmp == 0 ? getK() - o.getK() : cmp;
}
}
which yields the correct compiler error that you had subverted, with or
without a clause 'implements Comparable<SubC<SC>>'.
I also wonder about the 'extends BaseC<SC>' part. Generics aren't covariant
through the type parameter without some 'extends' magic, so I think this part
tangles the type assertions also. I haven't thought it through yet.
Anyway, you have found a corner where the generics assertions cannot handle
what you wanted to do. That part is true.
No, I haven't found a corner case generics cannot handle. The code at
https://gist.github.com/868085#file_self_consciousness_test.java
exactly shows how to handle the situation. The other example was to
It shows how to handle it incorrectly because of the 'Comparable' dodge.
demonstrate that the approach with class BaseC implements
Comparable<BaseC> does NOT work: https://gist.github.com/868085#file_failed_approach.java
Right, because it bumps up against the erasure issue.
It is also true that formally you
want to make different type assertions from those to properly implement
'Comparable'. When you make the correct type assertions, the compiler chokes
on the erasure demon.
You might be able to get away with the kind of self-referentiality you want
better with interfaces at the root than with classes. That's a good followup
line of inquiry.
Actually the working example is exactly what should be done here. We
Except for implementing 'Comparable' on the type parameter instead of the
class, which is not "exactly what should be done", it's "exactly what should
not be done" because it makes for convoluted, hard-to-understand and
not-exactly-what-you-want type assertions.
The problem is that you aren't declaring 'class Foo implements
Comparable<Foo>'. You're declaring 'class Foo implements Comparable<NotFoo>'.
want a class hierarchy where every class has its own default
ordering. It follows we want Comparable and it also follows that we
cannot use Comparable with a specific type but we need to propagate
You have found a workaround for the erasure issue. The problem is that you
created a generics structure that doesn't declare exactly what you mean and is
difficult to reason about. OTOH, the difficulty with the "normal" approach is
that you cannot get Java generics to do what you want that way.
the type parameter from the most specific type upwards. That requires
a type parameter at the base class and all sub classes which can have
sub classes that need a different type propagated.
Well, it requires that you cheat, in the sense that you have to warp the
declaration of 'implements Comparable'.
Notice I'm not saying one shouldn't do what you suggest, only that it requires
some dodgy type assertions to get around erasure and playing loose with the
semantics of 'implements Comparable'.
--
Lew
Honi soit qui mal y pense.