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?
T is a parameter that is associated with the generic type that is being defined in this class. To use the generic container, you must assign the type of the container by specifying it at instantiation using the angle bracket notation.
Yes, you can use any valid Java identifier for type parameters.
Well there's no difference between the first two - they're just using different names for the type parameter ( E or T ). The third isn't a valid declaration - ? is used as a wildcard which is used when providing a type argument, e.g. List<?>
To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound, which in this example is Number .
Sure - often these "self types" are used to constrain subtypes to return exactly their own type. Consider something like the following:
public interface Operation {
// This bit isn't very relevant
int operate(int a, int b);
}
public abstract class AbstractOperation<T extends AbstractOperation<T>> {
// Lets assume we might need to copy operations for some reason
public T copy() {
// Some clever logic that you don't want to copy and paste everywhere
}
}
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:
public class AddOperation extends AbstractOperation<AddOperation> {
// Methods etc.
}
And hence the copy()
method is guaranteed to return an AddOperation
. Now lets imagine we're silly, or malicious, or creative, or whatever, and try to define this class:
public class SubtractOperation extends AbstractOperation<AddOperation> {
// Methods etc.
// Because of the generic parameters, copy() will return an AddOperation
}
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 of SubtractOperation
would be legal, and you lose the guarantees about what T
is 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 copy
from another class altogther on an arbitrary Operation
, 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:
// This prelude is just to show that you don't even need to know the specific
// subclass for the type-safety argument to be relevant
Set<? extends AbstractOperation> operations = ...;
for (AbstractOperation<?> op : operations) {
duplicate(op);
}
private <T extends AbstractOperation<T>> Collection<T> duplicate(T operation) {
T opCopy = operation.copy();
Collection<T> coll = new HashSet<T>();
coll.add(operation);
coll.add(opCopy);
// Yeah OK, it's ignored after this, but the point was about type-safety! :)
return coll;
}
The assignment on the first line of duplicate
to T
wouldn'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.
Let's say you have a class called Animal
class Animal<T extends Animal>
If T is Dog, it would mean that Dog should be a subclass of Animal< Dog>, Animal< Cat>, Animal< Donkey> anything.
class Animal<T extends Animal<T>>
In this case if T is Dog, it has to be a subclass of Animal< Dog> and nothing else, i.e. you must have a class
class Dog extends Animal<Dog>
you can't have
class Dog extends Animal<Cat>
And even if you have that, you can't use Dog as a type parameter for Animal and you must have
class Cat extends Animal<Cat>
In many cases there is a lot of difference between the two, especially if you have some constraints applied.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With