问题
In the following sample code two classes EventA
and EventB
both implement the interface Historical
. Java can automatically cast an EventA
or EventB
to Historical
when one of these objects is passed as a parameter, as in the examineEvent
method below. However, Java is no longer able to cast when a generic is introduced ie. from List<EventA>
to List<Historical>
-- Unless the target function (in this case findClosestValidEventIndex
) is declared using List<? extends Historical>
.
Can someone explain why this must be? It seems to me that the very use of an interface in a generic should automatically imply the <? extends Interface>
.
public class SampleApplication {
public interface Historical {
public DateTime getDate();
}
public static class EventA implements Historical {
private DateTime date;
@Override
public DateTime getDate() {
return date;
}
}
public static class EventB implements Historical {
private DateTime date;
@Override
public DateTime getDate() {
return date;
}
}
private static int findClosestValidEventIndex(List<Historical> history, DateTime when) {
// do some processing
return i;
}
private static int examineEvent(Historical event){
return j;
}
public static void main(String[] args) {
DateTime target = new DateTime();
// AOK
EventA a = new EventA(target);
int idy = examineEvent(a);
// Type Error --- Unless
List<EventA> alist = new ArrayList<EventA>();
int idx = findClosestValidEventIndex(alist, target);
}
}
回答1:
Because List<EventA>
is not List<Historical>
. Imagine:
List<EventA> list = ...;
List<Historical> h = (List<Historical>) list;
h.add(new EventB()); //type-safety of list is compromised
for (EventA evt : list) { // ClassCastException - there's an EventB in the lsit
...
}
List<? extends Historical>
means "a list of a one specific subtype of Historical", and you cannot add anything to it, because at compile time you don't know what the type is.
回答2:
It can't always use extends
, because sometimes you may need different behaviour.
Covariance (extends
) is needed when generic object is used as a producer. When generic object is used as a consumer, you need contravariance (super
). Practical rule is "producer - extends, consumer - super":
public void getEventFromHistory(List<? extnds Historical> history) {
Historical e = history.get(0); // history is a producer of e
}
public void addEventAToHistory(List<? super EventA> history) {
history.add(new EventA()); // history is a consumer
}
// Possible usage
List<EventA> sequenceOfEventsA = ...;
addEventAToHistory(sequenceOfEventsA);
List<Historical> history = ...;
addEventToHistory(history);
回答3:
You cannot pass in a a collection bound to a subclass to a method argument which is bound to a collection of super class.
It's because the declaration List history by definition means that it is a list that is capable of holding anything that extends Historical. Which means you can add either EventA or EventB or a mix of those types into this collection.
But List<EventA> means a list which can support only objects of type EventA or a subtype of that. Such a list cannot contain EventB added into it.
Therefore, Java doesn't allow you to pass a List<EventA> as a valid argument to a method parameter which expects a List<Historical>. After all, though there is a inheritance relationship between Historical and EventA and Historical and EventB, there's no such relationship between a List<Historical> and a List<EventA>.
来源:https://stackoverflow.com/questions/6179638/java-casting-to-generic-with-interface-pointers