Java: Casting to Generic with Interface Pointers

人走茶凉 提交于 2019-12-24 03:07:32

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!