Problem outline
I'm generifying the better part of my current project's base and I had an idea that I decided to test regarding overriding an abstract method. Here are my test classes in Java:
public abstract class Base {
public abstract T test();
}
First implementation:
public class Inheritor extends Base {
@Override
public Inheritor test() {
return null;
}
}
Second implementation:
public class Inheritor2 extends Base {
@Override
public T test() {
return null;
}
}
Question 1
Why does it compile? I admit I had high hopes it would be legal, since it makes the contract not only ensure it returns something that does extend Base, but is more specialized already (so that I don't need to cast the result to my specialized class somewhere later).
All sounds nice but do I really fulfill the contract that the base class forces me into? My overriden implementation in Inheritor loses certain layer of genericness doesn't it? My implementation of this method in Inheritor doesn't ever return an instance of Inheritor2, possibility of which the abstract method seemed to enforce (as both extend Base).
I would like pointing to some excerpt from documentation. My guess is it has something to do with type erasure, would be nice if someone mentioned it's accuracy in his/her answer.
Question 2
Does this procedure have a formal name other than one I stated in the title?
Question 3
Is this possible in C#? Colleague's scratch test seemed to fail on compilation. Is there then a difference in approach to generic abstract method overriding?
解决方案
Here are the technicalities.
Concerning overriding:
An instance method mC declared in or inherited by class C, overrides
from C another method mA declared in class A, iff all of the following
are true:
A is a superclass of C.
C does not inherit mA.
The signature of mC is a subsignature (§8.4.2) of the signature of mA.
One of the following is true:
mA is public.
[...]
In your case, A is Base and C is Inheritor, Base#test() is mA and Inheritor#test() is mC.
mC is a subsignature of mA because
The signature of a method m1 is a subsignature of the signature of a
method m2 if either:
- m2 has the same signature as m1, or
- the signature of m1 is the same as the erasure (§4.6) of the signature of m2.
The erasure of mA is
public abstract Base test()
and mC
public Inheritor test()
If a method declaration d1 with return type R1 overrides or hides the
declaration of another method d2 with return type R2, then d1 must be
return-type-substitutable (§8.4.5) for d2, or a compile-time error
occurs.
Following the return-type-substitutable, we see
If R1 is a reference type then one of the following is true:
R1 can be converted to a subtype of R2 by unchecked conversion (§5.1.9).
Inheritor is a subtype of T extends Base through unchecked conversion, so we're all good (though you should have gotten a warning from your compiler).
So to answer your questions:
It compiles because of the rules declared in the Java Language Specification.
It's called overriding.
I don't have a full answer for you, but C# doesn't seem to have type erasure, so these rules wouldn't apply.
The dangers of unchecked conversion would allow you to do
class Inheritor extends Base {
@Override
public Inheritor test() {
return new Inheritor();
}
}
and then
Base ref = new Inheritor();
Inheritor2 wrong = ref.test();
which would cause a ClassCastException at runtime. Use it at your own risk.