Here is a code that compiles properly
public enum SupportedConversions {
INTEGER {
@Override
public Integer convert( String ion, Object[] aa) {
return null;
}
},
LONG {
@Override
public Long convert(final String ion, Object[] aa) {
return null;
}
};
public abstract <T> T convert(String val, Object[] aa);
}
But when i change the abstract function parameter to a List of objects instead of an array i get compilation error saying "Method does not override from super class". and this happens only if the return type is generic
example bad code
public enum SupportedConversions {
INTEGER {
@Override
public Integer convert( String ion, List<Object> aa) {
return null;
}
},
LONG {
@Override
public Long convert(final String ion, List<Object> aa) {
return null;
}
};
public abstract <T> T convert(String val, List<Object> aa);
}
Is there a reason for this to not work. Seems more like a bug in Java
The question should be “why can the first be compiled” rather than “why does the second fail”.
Both are broken.
A method signature like
<T> T convert(String val, Object[] aa)
says “whatever the caller substitutes for T
, this method will return a compatible result”. This isn’t very useful as the only valid return value is null
, but at least, the compiler will tell you, when you try to return an incompatible result within a method declared that way.
But the subclasses override this method like
Long convert(final String ion, Object[] aa)
in other words, override a method that promises to return whatever the caller wishes with a method that always returns Long
. That should feel wrong in the first place… The result is still compatible when you return null
, but not when you return a non-null
Long
value and the compiler won’t even warn you about that, as the Long
value is compatible with the declared return type of Long
.
The compiler should have issued a warning about the method declaration itself, however. To demonstrate the problem, with that declaration, you can write
String s = SupportedConversions.LONG.convert("bla", null);
and the compiler won’t object. As said, the base type declaration <T> T convert(…)
promises to return whatever the caller assumes for T
and here, T
has been inferred to be String
. That obviously will break at runtime, when the implementation returns a Long
instance.
The reason why this can be compiled at all, is the compatibility with pre-Generics code. The intention was to allow libraries with different “generification” state to interact. E.g. you can compile Java 1.4 application code with a recent jdk, even if some classes override now-Generic methods.
So the convert
method in your subclass not using Generics is allowed to override convert
method of the base class. In contrast, a method declaration like
Long convert(final String ion, List<Object> aa)
is using Generics, hence, is not allowed to bypass the Generic type system. If you use the raw type List
instead, you have again a non-Generic declaration which gets away with bypassing Generics.
If you are now tempted to say, it’s illogical to assume pre-Generics behavior here, you are not alone. Not only because the overriding method is within the same compilation unit (enum
declaration) than the Generic declaration that is overridden, both are inside an enum
declaration, a syntactical construct that didn’t exist prior to Java 5 (where Generics were introduced).
Further, the overriding methods utilize covariant return types, Long
resp. Integer
where the erasure of the method declaration has a return type of Object
, which also can’t appear in code prior to Java 5.
But these (still) are the rules. You should care for the compiler warnings here. If you didn’t get warnings (I know that the Netbeans IDE has sloppy defaults), you should try to enable them.
There is no fix for this code. What you are trying to do, is impossible with enum
s. You could remove the type parameter T
and let the base type’s method declaration return Object
, but the covariant return types in the enum
constants are irrelevant as they are not part of the public
API. The best alternative would be:
public interface SupportedConversions<T> {
SupportedConversions<Integer> INTEGER = (String ion, Object[] aa) -> {
return null;
};
SupportedConversions<Long> LONG = (String ion, Object[] aa) -> {
return null;
};
public abstract T convert(String val, Object[] aa);
}
resp.
public interface SupportedConversions<T> {
SupportedConversions<Integer> INTEGER = (ion, aa) -> {
return null;
};
SupportedConversions<Long> LONG = (ion, aa) -> {
return null;
};
public abstract T convert(String val, List<Object> aa);
// we can support both variants
public default T convert(String val, Object[] aa) {
return convert(val, Arrays.asList(aa));
}
}
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