Reputation: 187
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
Upvotes: 13
Views: 827
Reputation: 2865
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.
Upvotes: 5
Reputation: 10084
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.
Upvotes: 1