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
- This is based on ideas of @AdamGent .
- Unfortunately I am not fluent with JLS enough to prove the below from the spec.
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.