Why don't they introduce a feature with generics that allows self referencing?
Because that's harder than it seems at first glance. For instance, suppose you specify that "The name This
refers to the type of this
", and somebody writes the following:
class Number {
public abstract int compareTo(This other);
}
class Integer extends Number {
final int value;
public Integer(int value) {
this.value = value;
}
@Override
public int compareTo(This other) {
return value - other.value;
}
}
public static void main(String[] args) {
Number n1 = new Integer(42);
Number n2 = new Double(Math.PI);
n1.compareTo(n2);
}
Should this compile? Probably not, because the compareTo
implementation provided by class Integer
only works with Integers
, not some other subtype of Number
.
The problem is that our specification is ambiguous. When we said that "This
is the type of this
", did we mean the declared type of this
(i.e. class in whose source code the this
appears) or the subclass used to create the this
object at runtime?
If we choose the declared type, This
would means different things in a subclass than it its superclass. That would be very confusing. For instance:
class Super {
This delegate;
}
class Sub extends Super {
void foo() {
delegate.foo(); // error: delegate is of type "This", which does not have a method "foo"
}
}
If we choose the runtime type, the This
type is not known to the caller:
Number x = new Integer(42);
Number y = new Integer(43);
x.compareTo(y); // error: the method compareTo takes an argument of unknown type, but was provided a Number
meaning we can not invoke any method that takes a This
. We can't even do something as simple as:
class Super {
This data;
}
void temporarilyRemoveDataFrom(Super s) {
Super d = s.data;
s.data = null;
process(s);
s.data = d; // error: type Super is not assignable to an unknown subtype of Super
}
As you can see, introducing support for self-referential types raises all the issues of types that refer to arbitrary types. In particular, we need both type variance and a way to capture the value of unknown types.
Self referential types are therefore not significantly simpler than generics. In contrast, if we have generics, building a self referential type is trivial:
class SelfAware<T extends SelfAware<T>> {
abstract T getThis();
}
class Sub extends SelfAware<Sub> {
Sub getThis() {
return this;
}
}
SelfAware<Sub> x = new Sub();
x = x.getThis(); // compiles just fine
In addition, a case can be made that self-referential types are often overly constrained. Requiring programmers to define type variables and their bounds explicitly nudges them to think about which bounds are appropriate, avoiding accidental over-constraining. For instance, java.lang.Integer
does not implement Comparable<Integer>
, but the more general (and useful) Comparable<Number>
.
To conclude, subclassable self referential types are not significantly easier to use than normal generics, do not make the language more expressive, and tempt programmers to over-constrain type arguments, and increase the complexity of the language and its tooling for no clear benefit.
With all that said, let's return to the curious case of getClass()
:
How did the developers made it that you don't need to cast the return value of getClass()
?
By introducing special treatment for this method in the Java Language Specification, which writes:
The type of a method invocation expression of getClass
is Class<? extends |T|>
, where T
is the class or interface that was searched for getClass
(§15.12.1) and |T|
denotes the erasure of T
(§4.6).
It is worth noting that this method would have required special treatment even if self referential types were supported, because its interaction with the runtime type system exposes the caller to type erasure.
`. This kind of thing is sometimes used in builders that have hierarchies (i.e. `BaseBuilder`, `SpecialBuilder1`, `SpecialBuilder2` if you want the methods of `BaseBuilder` to still return a `SpecialBuilder1` if called on that object and not override each one).– Joachim Sauer Sep 11 '21 at 20:35