If I have a base class like this that I can't change:
public abstract class A {
public abstract Object get(int i);
}
and I try to extend it with a class B
like this:
public class B extends A{
@Override
public String get(int i){
//impl
return "SomeString";
}
}
everything is OK. But my attempt to make it more generic fails if I try:
public class C extends A{
@Override
public <T extends Object> T get(int i){
//impl
return (T)someObj;
}
}
I can't think of any reason why this should be disallowed. In my understanding, the generic type T
is bound to an Object
—which is the requested return type of A
. If I can put String
or AnyObject
as my return type inside B
, why am I not allowed to put <T extends Object> T
inside my C
class?
Another strange behavior, from my point of view, is that an additional method like this:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public <T extends Object> T get(int i){
//impl
}
}
is also not allowed, with the hint of a DuplicateMethod
provided. This one, at least, confuses me, and I think Java should make a decision: if it is the same return type, why not allow overriding; and if it is not, why shouldn't I be able to add this method? To tell me it's the same, but not allow it to be overridden, is very weird, based on common sense.
There's a conceptual problem. Suppose
C#get()
overridesA#get()
but
C#get()
requires aT
- what shouldT
be? There is no way to determine.You can protest that
T
is not required due to erasure. That is correct today. However erasure was considered a "temporary" workaround. The type system in most part does not assume erasure; it is actually carefully designed so that it can be made fully "reifiable", i.e. without erasure, in future version of java without breaking existing code.@RafaelT's original code results in this compilation error...
The simplest, most straightfoward solution to get @RafaelT's C subclass to compile successfully, is...
Although I'm sure other people meant well with their answers. Nevertheless, a few of the answers seem to have majorly misinterpreted the JLS.
The above change to only the class declaration, results in a successful compilation. That fact alone, means that @RafaelT's original signatures are indeed perfectly fine as subsignatures — contrary to what others have suggested.
I'm not going to make the same mistake that others who answered seem to have made, and try to pretend I fully grok the JLS generics documentation. I confess that I haven't figured out with 100% certainty, the root cause of the OP's original compilation failure.
But I suspect it has something to do with an unfortunate combination of the OP's use of
Object
as the return type on top of the typical confusing and quirky subtleties of Java's generics.From the JLS section 8.4.8.3 Overriding and hiding:
1 and 2 holds.
3 holds too because (quote from JLS section 8.4.2):
And you are having the other way: a method with generic type overriding one without generic.
4 holds too because the erased signatures are the same:
public Object get(int i)
You should read up on erasure.
In your example:
The compiler generated byte code for your generic method will be identical to your non-generic method; that is,
T
will be replaced with the upper bound, that beingObject
. That's why you're getting theDuplicateMethod
warning in your IDE.Well, for the first part, the answer would be that Java does not allow non-generic methods to be overridden by generic methods, even if the erasure is the same. It means that it wouldn't work even if you would just have the overriding method as:
I don't know why Java poses this limitation (gave it some thought), I just think it has to do with special cases implemented for sub-classing generic types.
Your second definition would essentially translate to:
which is obviously a problem.
Declaring method as
<T extends Object> T get(int i)
makes no sense without declaringT
somewhere else - in the method's arguments or in a field of the enclosing class. In the latter case, just parameterize the whole class with the typeT
.