3

I've an interface ITransportable that force the classes who implements it to have two methods, one for produce a object based on "this", and one to load "this" from a object. In fact I'm using "transports" object to hide the underlying classes to the user, so every class that is "transportable", in this example "ItemDetalle" have a corresponding transport class. The GenericTransport class implements some generics methods I use to make the code more readable and all transports classes derive from it.

public interface ITransportable<T> { 
  public T getTransport();
  public <U extends ITransportable<T>> U loadFromTransport(T transport);
}

public class GenericTransport {
  [...]
  public static <T extends ITransportable<U>,U extends GenericTransport> T loadFromTransport(U transport) {
    try {
      Method loadFromTransport=transport.getClass().getMethod("loadFromTransport");
      T result = (T) loadFromTransport.invoke(transport);
      return result;
    } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
      Logger.getLogger(GenericTransport.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }

  public static <T,U extends GenericTransport> LinkedList<T> loadFromTransport(List<U> list) {
    if (list==null) return null;
    LinkedList<T> ll=new LinkedList<>();
    for (U element : list) ll.add((T) GenericTransport.loadFromTransport(element));
    return ll;
  }

}

here there are two "twin" classes, ItemDetalle class and ItemDetalleTransport

public ItemDetalleTransport extends GenericTransport {
  [...]
}

public class ItemDetalle implements ITransportable<ItemDetalleTransport> {
  public ItemDetalleTransport getTransport() {
    [...]
  }
  public void loadFromTransport(ItemDetalleTransport t) {
    [...]
  }
}

and there is a class that I use for experimenting:

public class otherClass {
  protected LinkedList<ItemDetalle> det
  public void setDet(LinkedList<ItemDetalle> det) {
    this.det=det;
  }
  [...]
  public void test1(ItemDetalleTransport t) {
    ItemDetalle det = GenericTransport.loadFromTransport(t);    
  }
  public void test2(LinkedList<ItemDetalleTransport> t) {
    LinkedList<ItemDetalle> localDet = GenericTransport.loadFromTransport(t);    
    this.setDet(localDet);
  }
  public void test3(LinkedList<ItemDetalleTransport> t) {
    this.det = GenericTransport.loadFromTransport(t);    
  }
  public void test4(LinkedList<ItemDetalleTransport> t) {
    this.setDet(GenericTransport.loadFromTransport(t));
  }
  [...]
}

function "test4" does not work, claiming that a LinkedList of Objects cannot be converted to a LinkedList of ItemDetalle. I understand it's not a very big problem, but I'm curious, someone can explain why?

Thank you!

moretti.fabio
  • 1,128
  • 13
  • 32
  • possible duplicate of [Is List a subclass of List? Why aren't Java's generics implicitly polymorphic?](http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p) – Harry Blargle Jul 05 '14 at 23:19
  • I've read the post and I don't think mine is a duplicate: I've arguing why the setDet doesn't work but assigning directly to the variable work. In fact setDet is declared to accept the same type of the variable, so there's a difference between assigning List directly or pass through a function. – moretti.fabio Jul 05 '14 at 23:54

1 Answers1

1

In Java, Generics are invariant, and I'll explain with an example.

Say you have a class Parent and another class Child which extends Parent. Generics being invariant means that List<Child> will not be considered as List<Parent> unlike arrays (Child[] arr is instanceof Parent[]).

The reason that Generics are invariant is that its implemented by erasure - which means that they enforce their type constraints only at compile time and discard their type information at runtime (again, unlike arrays). Since Generics can't enforce their type during runtime, and in order to be safe to use - Generics were made invariant.

Addendum: Due to the discussion below in the comments, reading this and this teaches us that arrays were made covariant as a trade-off between type-safety and the possibility to make your program more polymorphic. That trade-off was not needed when Generics were introduced because along with them came wildcards which enabled expressions of covariance. Many thanks to @user3580294!

Community
  • 1
  • 1
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129
  • 1
    You were right up until the last paragraph. If your last paragraph were right, C#'s generics would be covariant -- but [they aren't](http://msdn.microsoft.com/en-us/library/aa479859.aspx#fundamentals_topic12). Invariant generics were made because covariance would violate type safety, as pointed out by Jon Skeet [here](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p/2745301#2745301). Erasure doesn't have anything to do with it. – awksp Jul 05 '14 at 21:59
  • @user3580294 Long time no seen pal! We're arguing about semantics here, and I'll explain: I agree with your sentence: "Invariant generics were made because covariance would violate type safety" but, why wasn't the same thing done with arrays ? (and indeed arrays are *not* safe from that reason!). Since Generics are implemented by erasure - it **cannot** be checked during runtime cause during runtime the information is simply not there. In order to make it safer to use Generics had to be more stricted (hence - invariant). Can you agree with that ? – Nir Alfasi Jul 05 '14 at 23:31
  • Hello to you too! The reason arrays are covariant is explained [here](https://stackoverflow.com/questions/18666710/why-are-arrays-covariant-but-generics-are-invariant), and I think I remember Jon Skeet explaining somewhere why he things array covariance was a mistake (He says that in the comments to [this](http://stackoverflow.com/a/2745301) answer, but doesn't elaborate why). I can agree with your last sentence, but I don't really agree with the sentence before, because again, C#'s generics are reified, yet they also are invariant, so erasure can't be the *only* reason generics are invariant. – awksp Jul 05 '14 at 23:38
  • From my understanding, covariance would violate the *compile-time* type safety guarantees that generics were supposed to provide, which is why generics were designed to be invariant. If generics were covariant, you wouldn't have any type safety guarantees, whether they were erased as in Java or kept as in C#. – awksp Jul 05 '14 at 23:40
  • @user3580294 I have to admit that I don't know C# and its implementation of generics but I can definitely agree with Jon's "second thoughts" about making arrays covariant (though its reified). I edited the last paragraph. By the way, Jon's example with Animal/Dog/Cat is priceless. I should bookmark it :) – Nir Alfasi Jul 05 '14 at 23:42
  • The only reason I happen to know C#'s implementation of generics is because I've read so many complaints about how Java did it :P And yeah, he really knows how to make things interesting – awksp Jul 05 '14 at 23:50
  • 1
    @user3580294 LOL! now I know too... after we started this discussion I also found [*Paul's answer*](http://stackoverflow.com/a/18666878/1057429) (which also quotes Jon's) with some additional interesting information in regards to the reasons for making arrays covariant in the first place and why the same reasons didn't apply to generics (because of the existance of wildcards). Nice! I learned something new today - thanks you! – Nir Alfasi Jul 05 '14 at 23:53
  • 1
    No problem, glad to know I could help you learn more! Interestingly, if I remember correctly C# doesn't have Java's wildcards, so there are some other things you need to do to get the equivalent code, but I don't have nearly enough C# experience to say exactly what... – awksp Jul 06 '14 at 00:07