2

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);
   }
}
Toaster
  • 1,911
  • 2
  • 23
  • 43

3 Answers3

7

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.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
3

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);
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • The rule you present sounds useful but I am not sure I understand the need for `? super`. Is this basically declaring a weakly typed Object pointer? Are you still able to use the super class's methods when accessing a generic method of this type? – Toaster May 31 '11 at 17:35
  • @Colin: Didn't get your comment. Perhaps updated code sample makes more sense. – axtavt Jun 01 '11 at 08:41
1

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>.

Yohan Liyanage
  • 6,840
  • 3
  • 46
  • 63