Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to CRTP in Java [closed]

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.

like image 661
Bass Avatar asked Oct 02 '18 15:10

Bass


1 Answers

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.

like image 182
newacct Avatar answered Oct 16 '22 02:10

newacct