可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
JLS # 8.4.2. Method Signature
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.
As per above rule as your parent do not have an erasure and your child has one so it is not a valid overriding.
JLS#8.4.8.3. Requirements in Overriding and Hiding
Example 8.4.8.3-4. Erasure Affects Overriding
A class cannot have two member methods with the same name and type erasure:
class C<T> {
T id (T x) {...}
}
class D extends C<String> {
Object id(Object x) {...}
}
This is illegal since D.id(Object) is a member of D, C.id(String) is declared in a supertype of D, and:
- The two methods have the same name, id
- C.id(String) is accessible to D
- The signature of D.id(Object) is not a subsignature of that of
C.id(String)
- The two methods have the same erasure
Two different methods of a class may not override methods with the same erasure:
class C<T> {
T id(T x) {...}
}
interface I<T> {
T id(T x);
}
class D extends C<String> implements I<Integer> {
public String id(String x) {...}
public Integer id(Integer x) {...}
}
This is also illegal, since D.id(String) is a member of D, D.id(Integer) is declared in D, and:
- The two methods have the same name, id
- D.id(Integer) is accessible to D
- The two methods have different signatures (and neither is a
subsignature of the other)
- D.id(String) overrides C.id(String) and D.id(Integer)
overrides I.id(Integer) yet the two overridden methods have the same
erasure
Also It gives example of a case where it is allowed from super to child
The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.
Consider the example:
class CollectionConverter {
List toList(Collection c) {...}
}
class Overrider extends CollectionConverter {
List toList(Collection c) {...}
}
Now, assume this code was written before the introduction of generics, and now the author of class CollectionConverter decides to generify the code, thus:
class CollectionConverter {
<T> List<T> toList(Collection<T> c) {...}
}
Without special dispensation, Overrider.toList would no longer override CollectionConverter.toList. Instead, the code would be illegal. This would significantly inhibit the use of generics, since library writers would hesitate to migrate existing code.
回答2:
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:
public <T extends Object> Object get(int i)
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:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public Object get(int i){
//impl
}
}
which is obviously a problem.
回答3:
From the JLS section 8.4.8.3 Overriding and hiding:
It is a compile-time error if a type declaration T has a member method m1 and there exists a method m2 declared in T or a supertype of T such that all of the following conditions hold:
m1 and m2 have the same name.
m2 is accessible from T.
The signature of m1 is not a subsignature (§8.4.2) of the signature of m2.
The signature of m1 or some method m1 overrides (directly or indirectly) has the same erasure as the signature of m2 or some method m2 overrides (directly or indirectly).
1 and 2 holds.
3 holds too because (quote from JLS section 8.4.2):
The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method.
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)
回答4:
Imagine the following invocation.
A a = new C();
a.get(0);
In effect you are calling a generic method, yet you're not passing in any type arguments. As things stand, this is not much of a problem. Those type arguments disappear during code generation anyways. Yet reification has never been taken off the table and the stewards of Java the language have tried and continue to try and keep that door open. If type arguments were reified, your invocation would not provide any to a method that requires one.
回答5:
@RafaelT's original code results in this compilation error...
C.java:3: error: C is not abstract and does not override abstract method get(int) in A
public class C extends A {
^
C.java:5: error: name clash: <T>get(int) in C and get(int) in A have the same erasure, yet neither overrides the other
public <T extends Object> T get ( int i ){
^
where T is a type-variable:
T extends Object declared in method <T>get(int)
The simplest, most straightfoward solution to get @RafaelT's C subclass to compile successfully, is...
public abstract class A {
public abstract Object get(int i);
}
public class C<T> extends A {
@Override
public T get(int i){
//impl
return (T)someObj;
}
}
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.
回答6:
You should read up on erasure.
In your example:
public class D extends A{
@Override
public Object get(int i){
//impl
}
public <T extends Object> T get(int i){
//impl
}
}
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 being Object
. That's why you're getting the DuplicateMethod
warning in your IDE.
回答7:
Declaring method as <T extends Object> T get(int i)
makes no sense without declaring T
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 type T
.
回答8:
There's a conceptual problem. Suppose C#get()
overrides A#get()
A a = new C();
Object obj = a.get(); // actually calling C#get()
but C#get()
requires a T
- what should T
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.