I have two interfaces that look like this:
interface Parent<T extends Number> {
T foo();
}
interface Child<T extends Integer> extends Parent<T> {
}
If I have a raw Parent
object, calling foo()
defaults to returning a Number
since there is no type parameter.
Parent parent = getRawParent();
Number result = parent.foo(); // the compiler knows this returns a Number
This makes sense.
If I have a raw Child
object, I would expect that calling foo()
would return an Integer
by the same logic. However, the compiler claims that it returns a Number
.
Child child = getRawChild();
Integer result = child.foo(); // compiler error; foo() returns a Number, not an Integer
I can override Parent.foo()
in Child
to fix this, like so:
interface Child<T extends Integer> extends Parent<T> {
@Override
T foo(); // compiler would now default to returning an Integer
}
Why does this happen? Is there a way to have Child.foo()
default to returning an Integer
without overriding Parent.foo()
?
EDIT: Pretend Integer
isn't final. I just picked Number
and Integer
as examples, but obviously they weren't the best choice. :S
Imagine public interface Parent<T extends Number>
was defined in a different compilation unit - in a separate file Parent.java
.
Then, when compiling Child
and main
, the compiler would see method foo
as Number foo()
. Proof:
import java.lang.reflect.Method;
interface Parent<T extends Number> {
T foo();
}
interface Child<R extends Integer> extends Parent<R> {
}
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(Child.class.getMethod("foo").getReturnType());
}
}
prints:
class java.lang.Number
This output is reasonable as java does type erasure and is not able to retain T extends
in the result .class
file plus because method foo()
is only defined in Parent
. To change the result type in the child compiler would need to insert a stub Integer foo()
method into the Child.class
bytecode. This is because there remains no information about generic types after compilation.
Now if you modify your child to be:
interface Child<R extends Integer> extends Parent<R> {
@Override R foo();
}
e.g. add own foo()
into the Child
the compiler will create Child
's own copy of the method in the .class
file with a different but still compatible prototype Integer foo()
. Now output is:
class java.lang.Integer
This is confusing of course, because people would expect "lexical visibility" instead of "bytecode visibility".
Alternative is when compiler would compile this differently in two cases: interface in the same "lexical scope" where compiler can see source code and interface in a different compilation unit when compiler can only see bytecode. I don't think this is a good alternative.
The T
s aren't exactly the same. Imagine that the interfaces were defined like this instead:
interface Parent<T1 extends Number> {
T1 foo();
}
interface Child<T2 extends Integer> extends Parent<T2> {
}
The Child
interface extends the Parent
interface, so we can "substitute" the formal type parameter T1 with the "actual" type parameter which we can say is "T2 extends Integer"
:
interface Parent<<T2 extends Integer> extends Number>
this is only allowed because Integer is a subtype of Number. Therefore, the signature of foo()
in the Parent
interface (after being extended in the Child
interface) is simplified to:
interface Parent<T2 extends Number> {
T2 foo();
}
In other words, the signature is not changed. The method foo()
as declared in the Parent
interface continues to return Number
as the raw type.
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