The issue of variance (particularly contravariance) has got me banging my head against the wall for a week. I have finally understood the theory, thanks to a couple of questions on here, and now as soon as I start working on it, I am getting errors I just don't understand.
I have a simple heirarchy of classes:
abstract class Fruit, Mango extends Fruit, Orange extends Fruit, BloodOrange extends Orange
abstract class Fruit implements PlantEatable {
private boolean isRipe;
private boolean isEatable;
public boolean isRipe() {
return isRipe;
}
public void setRipe(boolean ripe) {
isRipe = ripe;
}
@Override
public boolean isEatable() {
return isEatable;
}
public void setEatable(boolean eatable) {
isEatable = eatable;
}
}
public class Mango extends Fruit {
}
public class Orange extends Fruit{
}
public class BloodOrange extends Orange{
}
Now, The Oracle documentation generally has been great about Generics except for the part that was the most important I find confusing: https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html
If I am doing PECS which is Producer Extends Consumer Super
I am trying to do something very simple:
public class UpperBoundEg {
public static <E> void copy(List<? extends E> src, List<? super E> dest) {
src.forEach( item -> {
dest.add(item);
System.out.println("Added to dest: " + item.getClass());
});
}
public static <E> void acceptTest(List<? super Orange> objects) {
}
public static void main(String[] args) {
//Producer
List<? extends Orange> oranges = new ArrayList<>();
//oranges.add(new Orange()); // Doesn't work because its a producer ?
//Consumer
List<? super BloodOrange> objects = new ArrayList<>();
objects.add(new BloodOrange());
//objects.add(new Orange()); // Why doesn't it work ?
//objects.add(new Object()); // Why doesn't it work ?
copy(
Arrays.asList(
new Orange(),
new Orange(),
new BloodOrange(),
new Object(),
new Mango() // Why is this allowed?
),
new ArrayList<>()
);
}
}
Why is this happenning ? I thought List<? super BloodOrange> should take BloodOrange and all its super classes ? Why is it only accepting BloodOrange ?
And Why am I able to add a Mango to the copy function ?

You seem to think a
List<? extends Orange> orangesmeans: The list referred to by the variable namedorangescan contain 'anything that is either an Orange, or some subtype of it'.But that is wrong.
After all, if that's what you wanted, you would just write
List<Orange>. Given:The type of the
bvariable isOrange, but the object it is pointing at, is actually a BloodOrange. Which is fine. All bloodoranges are oranges. But, therefore, obviously:The above compiles completely fine. I just added a bloodorange to a
List<Orange>. Which, of course, I can do: All BloodOranges are Oranges. So why wouldn't I be able to?Hence, for the meaning 'a list that contain anything that is either an Orange or some subtype of it',
List<Orange>is it. NotList<? extends Orange>.List<? extends Orange>has a different meaning. It means:This is a list that is restricted to contain.. I don't actually know. It has some, unknown to this code, restriction. However, what I do is, that the nature of the restriction is either that it can only contain Oranges (and therefore, BloodOranges are also fine), or, some subtype of Orange.
In other words, this is legal:
and now you see why you can't call
.add(new Orange())on aList<? extends Orange>. After all, java is reference based, the above code contains only onenewstatement so there is just one list, period. You merely have 2 variables that both point at the same list - it's like having an address book with 2 separate pages both listing the same address - that doesn't magically mean there are now somehow 2 houses. So, if you add something tosomeSortOfOrangesList, you're also adding it to the list pointed at by variablebloodOranges, given that both variables are pointing at the same list. Thus, if you COULD add.add(new Orange())to aList<? extends Orange>, then I just added a NOT BloodOrange to a list whose type isList<BloodOrange>and I just broke it. Hence why the compiler won't let you, at all.Once you understand this, all is clear:
Same reason:
List<? super BloodOrange>does not mean 'this list can contain bloodoranges or any supertype of it'. Because that's completely pointless - if you want that, just writeList<Object>. No, it means: "This List has some unknown to be restriction, however, I do know, that restriction is either BloodOrange, or Orange, or Fruit, or Object. One of those 4, I don't know which one, but.. it has to be one of those 4 or the compiler wouldn't have allowed me to write the assignment".And with that specific restriction, you CAN add BloodOrange instances to this list. Because
.add(new BloodOrange())is fine on aList<Orange>, it is also fine on aList<BloodOrange>, and also fine on aList<Fruit>, and also fine on aList<Object>. We know it has to be one of those 4, so it is fine.You can't call
.add(new Orange())on this list because that is fine for 3 of the 4 things it could be, but not if yourList<? super BloodOrange>variable is pointing at a list whose type isList<BloodOrange>. Because then you'd be adding a not-BloodOrange to it, we don't want that.Because java will figure out that picking
Objectas a bound works here. Because they're all objects.A crucial thing you need to understand here is that
class Foo extends Barmeans: "Any instance of Foo is just as suitable as a Bar and can be used anytime, anywhere, a Bar is required; the reverse is not necessarily true of course", therefore, they are ALL objects and can ALL be used where anObjectwould be required. Thus, java goes: Aight, we'll make thatEbound to Object, turning it into requiring [A]List<? extends Object>and [B] aList<? super Object>- andList<Object>fits both of those bounds. So, we interpret theList.asList(...)asList.<Object>asList(...)which is fine as every argument to it is an object (all things are objects, so that's obvious then), and we'll interpretnew ArrayList<>()asnew ArrayList<Object>()and voila that will compile just fine. Hence, that's what happens here.