The CRTP pattern allows to emulate the so called self types in Java, e. g.:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> implements Comparable<SELF> {
@Override
public final int compareTo(final SELF o) {
// ...
}
}
final class C1 extends AbstractFoo<C1> {
// ...
}
final class C2 extends AbstractFoo<C2> {
// ...
}
With the above code (the Comparable
interface has been chosen just for clarity; of course there're other use cases), I can easily compare two instances of C1
:
new C1().compareTo(new C1());
but not AbstractFoo
descendants whose concrete types are different:
new C1().compareTo(new C2()); // compilation error
It's easy to abuse the pattern, however:
final class C3 extends AbstractFoo<C1> {
// ...
}
// ...
new C3().compareTo(new C1()); // compiles cleanly
Also, the type checks are purely compile-time ones, i. e. one can easily mix C1
and C2
instances in a single TreeSet
and have them compared with each other.
What alternative to CRTP in Java emulates self types without the potential for abuse as shown above?
P. S. I observe the pattern is not widely used in the standard library -- only EnumSet
and its descendants implement it.
I don't think what you showed is "abuse". All the code that uses AbstractFoo
and C3
are still perfectly type-safe, as long as they don't do any unsafe casts. The bounds of SELF
in AbstractFoo
means that the code can rely on the fact that SELF
is a subtype of AbstractFoo<SELF>
, but the code cannot rely on the fact that AbstractFoo<SELF>
is a subtype of SELF
. So for example, if AbstractFoo
had a method that returned SELF
, and it was implemented by returning this
(as should be possible if it were really a "self-type"), it would not compile:
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF someMethod() {
return this; // would not compile
}
}
The compiler doesn't let you compile this because it is unsafe. For example, running this method on C3
would return this
(which is really a C3
instance) as type C1
, which would cause a class cast exception in the calling code. If you tried to sneak past the compiler by using a cast, like return (SELF)this;
, then you get an unchecked cast warning which means you take responsibility for it being unsafe.
And if your AbstractFoo
is really used in a way that only relied on the fact that SELF extends AbstractFoo<SELF>
(as the bound says), and does not rely on the fact that AbstractFoo<SELF> extends SELF
, then why do you care about the "abuse" of C3
? You can still write your classes C1 extends AbstractFoo<C1>
and C2 extends AbstractFoo<C2>
fine. And if someone else decides to write a class C3 extends AbstractFoo<C1>
, then as long as they write it in a way without using unsafe casts, the compiler guarantees that it is still type-safe. Perhaps such a class might not be able to do anything useful; I don't know. But it is still safe; so why is that a problem?
The reason why a recursive bound like <SELF extends AbstractFoo<SELF>>
is not used much is that, in most cases, it is not any more useful than <SELF>
. For example, the Comparable
interface's type parameter doesn't have a bound. If someone decides to write a class Foo extends Comparable<Bar>
, they can do so, and it is type-safe, though not very useful, because in most classes and methods that use Comparable
, they have a type variable <T extends Comparable<? super T>>
, which requires that T
is comparable to itself, so the Foo
class would not be usable as a type argument in any of those places. But it is still fine for someone to write Foo extends Comparable<Bar>
if they want.
The only places where a recursive bound like <SELF extends AbstractFoo<SELF>>
is in a place which actually makes use of the fact that SELF
extends AbstractFoo<SELF>
, which is quite rare. One place is in something like the builder pattern, which has methods that return the object itself, which can be chained. So if you have methods like
abstract class AbstractFoo<SELF extends AbstractFoo<SELF>> {
public SELF foo() { }
public SELF bar() { }
public SELF baz() { }
}
and you had a general value of AbstractFoo<?> x
, you can do things like x.foo().bar().baz()
which you could not do if it were declared as abstract class AbstractFoo<SELF>
.
There isn't a way in Java Generics to make a type parameter that must be the same type as the current implementing class. If, hypothetically, there were such a mechanism, that could cause tricky problems with inheritance:
abstract class AbstractFoo<SELF must be own type> {
public abstract int compareTo(SELF o);
}
class C1 extends AbstractFoo<C1> {
@Override
public int compareTo(C1 o) {
// ...
}
}
class SubC1 extends C1 {
@Override
public int compareTo(/* should it take C1 or SubC1? */) {
// ...
}
}
Here, SubC1
implicitly inherits AbstractFoo<C1>
, but that breaks the contract that SELF
must be the type of the implementing class. If SubC1.compareTo()
must take a C1
argument, then it is no longer true that the type of the thing received is the same type as the current object itself. If SubC1.compareTo()
can take a SubC1
argument, then it is no longer overrides C1.compareTo()
, as it no longer takes as wide a set of the arguments as the method in the superclass takes.
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