Consider the following code:
import java.util.Arrays;
import java.util.List;
class Person {}
class OtherPerson {}
class SomeDummyClass {}
interface A {
<E>List<E> foo();
}
class B implements A {
@Override
public List<Object> foo() {
return Arrays.asList(Integer.valueOf(3), new Person(), new OtherPerson());
}
}
public class Main {
public static void main(String[] args) {
A a = new B();
List<SomeDummyClass> someDummyClasses = a.foo();
System.out.println(someDummyClasses.get(0)); // prints 3
}
}
I'm confused why we can override the foo()
method without any compile time errors. This particularly became less intuitive to reason about when I wrote List<SomeDummyClass> someDummyClasses = a.foo();
and this is perfectly valid (no compile time error).
A real time example of this case is with mybatis-3
.
A
mimics ResultHandler
B
mimics DefaultResultSetHandler
(This class is not package private - which I expected it should have been) foo()
mimics handleResultSets
List<SomeDummyClass> someDummyClasses = a.foo();
not giving any compile time error. But at runtime, I thought it should have given a ClassCastException
. What's actuall happening here? System.out.println(someDummyClasses.get(0));
I get a ClassCastException
. Why is this so? I'm confused why we can override the foo() method with any compile time errros.
Yes but you have a compilation warning since the return type in the overrided method List<Object>
is more specific that List<E>
in the interface method :
Unchecked overriding: return type requires unchecked conversion. Found
java.util.List<java.lang.Object>
, requiredjava.util.List<E>
.
To not handle this kind of warning may lead to an inconsistent use of the generics. And there you have a very good example.
About :
This particularly became less intuitive to reason about when I wrote List someDummyClasses = a.foo(); and this is perfectly valid (no compile time error).
And why would you get a compilation error ?
Here you declare a
as a A
:
A a = new B();
So here :
List<SomeDummyClass> someDummyClasses = a.foo();
at compile time, foo()
refers to the <E> List<E> foo()
method declared in the interface and a method scoped generic with E
that is not bounded (so is Object
) is designed to adapt the declared return type to the target : here that is the type of the variable declared by the client of the method to assign the method return.
Change your declaration to :
B b = new B();
List<SomeDummyClass> someDummyClasses = b.foo();
And the compilation would not even pass.
Note that your issue is not specific to overriding method. Declare a method (that also produces a warning about unchecked cast) :
public static <T> List<T> getListOfAny(){
return (List<T>) new ArrayList<Integer>();
}
And you could use it in the same inconsistent way :
List<String> listOfAny = getListOfAny();
The moral of this story : don't consider unchecked conversions warnings produced by the compiler like cosmetics/details but handle them correctly and you could benefit as much as possible from the compilation check brought by generics.
But at runtime, I thought it should have given a ClassCastException. What's actually happening here?
Remember that generics in Java are purely a compile-time thing. When your code is compiled, the compiler removes all the generic parameters, and adds casts where necessary. Your code at runtime looks like this:
interface A {
List foo();
}
class B implements A {
@Override
public List foo() {
return Arrays.asList(Integer.valueOf(3), new Person(), new OtherPerson());
}
}
// ...
List someDummyClasses = a.foo();
System.out.println(someDummyClasses.get(0));
Look! Nothing is wrong here. There are no casts, let alone ClassCastException
s.
There would be an ClassCastException
if you did this:
SomeDummyClass obj = someDummyClasses.get(0);
Because the above code will become this at runtime:
SomeDummyClass obj = (SomeDummyClass)someDummyClasses.get(0);
When I do System.out.println(someDummyClasses.get(0)); I get a
ClassCastException
. Why is this so?
Is there a better way to re-write these so that the code becomes less fragile?
(Note that I don't know much about mybatis)
I think what you should do here is to make A
a generic interface:
interface A<T> {
List<T> foo();
}
And make B implements A<Object>
. This way, at least it's more type-safe.
1st) The signature of A#foo is return a <E> List<E>
that will return a list of the type being captured as E on asssignment, List<X> = new A().foo()
(if A was a concrete class) compiles since it captures the type, while List<X> = new B().foo()
return a List<Object>
and cannot be assigned.
2nd) You get a ClassCastException since you assigned Integers, and Strings to a List
3rd) Your interface could have like <E extends SummyDummyClass> List<E> foo();
turning the declaration on B class invalid and so on, also you will need less casts since you know in advance the type of the superclass of E
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.