1

I'm struggling to understand why the following code doesn't work :

public <E extends Animal & IQuadruped> void testFunction()
{
    List<E> list = new ArrayList<E>();
    Dog var = new Dog();
    list.add(var);
}

with Dog being the following :

public class Dog extends Animal implements IQuadruped
{

}

And I get a compile error on the add :

The method add(E) in the type List<E> is not applicable for the arguments (Dog)

I just want to make sure my list elements extend/implement both classes, and Dog fullfil those conditions, why is it not working ?

Thank you

Th4n4to
  • 49
  • 2
  • 6
  • 6
    I could call `YourClass.testFunction()`, and then your function would create an `ArrayList` and add a Dog to it. – user253751 Jul 10 '15 at 14:44
  • Ha I see now the flaw in the logic, I modified it like that then : `code` public void testFunction(E var) { List list = new ArrayList(); list.add(var); } `code` and called it : `code` Dog var = new Dog(); testFunction(var); `code` That works and makes more sense, thanks :) – Th4n4to Jul 10 '15 at 15:10
  • If on the other hand you took the element as a parameter, like this: `public void testFunction(E var)`, it would work. – biziclop Jul 10 '15 at 15:13

2 Answers2

1

What <E extends Animal & IQuadruped> means is "a particular type that is a subtype of both Animal and IQuadruped", and not "any type that is a subtype of both Animal and IQuadruped"

The reason it's difficult to grasp the difference is that in everyday thinking we don't make that distinction explicit. If for example you agree to go out for lunch with someone in a restaurant, and they say "any day next week is good for me", you automatically know that means you need to turn up on the same day. And not that you can go at any time and they'll be there.

In this case there's no guarantee that the E the caller chooses will definitely be Dog, so your code won't work.

The obvious wrong solution is to specify<E extends Dog>, because it would guarantee that E is a subtype of Dog. However this is wrong for the exact same reason, E could be any subtype of Dog, let's say E is a Poodle, so when you create a List<Poodle>, you won't be able to put your new Dog() in there, because it isn't a Poodle.

The correct bound is <E super Dog>, because that means E is definitely a type that you can cast a Dog instance to. It could be Dog itself, it could be Animal or IQuadruped or even Object. But it guarantees that you can put a Dog in the list.

The name of this principle is PECS: producer extends, consumer super, and you can read about it here.

Community
  • 1
  • 1
biziclop
  • 48,926
  • 12
  • 77
  • 104
0

Generics type erasure is what you are getting. By the time the JVM executes the line where you are adding to the list, the generics is already lost. To get this to work, you would have to do something like:

public <E extends Animal & IQuadruped> void testFunction(E param) {
    List<E> list = new ArrayList<E>();
    list.add(param);
}

public void addDog() {
    testFunction(new Dog())
}
Ratshiḓaho Wayne
  • 743
  • 1
  • 5
  • 23
  • 2
    Even without type erasure this would be invalid. The problem is around covariance and contravariance. – biziclop Jul 10 '15 at 15:13
  • I'm downvoting this because the problem has nothing to do with type erasure. (That would be the case if he tried to do `new E()`, though) – user253751 Jul 11 '15 at 02:05