Is there a semantic difference between these two declaration or is it only syntactic sugar?
class C<T extends C> vs class C<T extends C<T>>
Background: I recently answered a question on generics using the C<T extends C> approach and a peer provided a similar answer based on C<T extends C<T>>. At the end, both alternatives provided the same result (in the context of the question asked). I remained curious about the difference between these two constructs.
Is there’s a semantic difference? If so, what are the implications and consequences of each approach?
Sure – often these “self types” are used to constrain subtypes to return exactly their own type. Consider something like the following:
Cool – we have a parent class with a useful operator which can be specific to subclasses. For instance, if we create an
AddOperation, what can its generic parameters be? Because of the “recursive” generic definition, this can only be AddOperation giving us:And hence the
copy()method is guaranteed to return anAddOperation. Now lets imagine we’re silly, or malicious, or creative, or whatever, and try to define this class:This will be rejected by the compiler because the generic type isn’t within its bounds. This is quite important – it means that in the parent class, even though we don’t know what the concrete type is (and it might even be a class that didn’t exist at compile time), the
copy()method will return an instance of that same subclass.If you simply went with
C<T extends C>, then this weird definition ofSubtractOperationwould be legal, and you lose the guarantees about whatTis in that case – hence the subtract operation can copy itself into an add operation.This isn’t so much about protecting your class hierarchy from malicious subclasses, it’s more that it gives the compiler stronger guarantees about the types involved. If you’re calling
copyfrom another class altogther on an arbitraryOperation, one of your formations guarantees that the result will be of the same class, while the other will require casting (and might not be a correct cast, as with the SubtractOperation above).Something like this for example:
The assignment on the first line of
duplicatetoTwouldn’t be type-safe with the weaker of the two bounds you proposed, so the code wouldn’t compile. Even if you define all of the subclasses sensibly.