Reputation: 31
import java.util.ArrayList;
import java.util.List;
public class WildCardNumber {
public static void main(String[] args) {
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(50));// * Compile time error
ln.add(new EvenNumber(46)); // ** Compile time error
}
}
class NaturalNumber {
private int n;
public NaturalNumber(int n) {
this.n = n;
}
}
class EvenNumber extends NaturalNumber {
public EvenNumber(int n) {
super(n);
}
}
While studying Wildcards in Oracle docs, I found the aforementioned code.
According to the source the variable "ln" can not accept any "NaturalNumber"s, since it is a list of "EvenNumber" contents. I tried adding a "EvenNumber" object to it. That is also not being accepted.
It seems the variable "ln" is a read only object as mentioned in the doc. Could you guys please explain as to why this object is read-only ?( I am able to add null values though) If we are not able to add "NaturalNumber"s, why can't we add "EvenNumber"s too ? Since according to Wildcard we have specified, that the variable "ln" can accept subtypes of "NaturalNumber", and "EvenNumber" being the subtype?
Upvotes: 2
Views: 78
Reputation: 181689
Type safety checks are based on the declared type of the object involved. This declaration
List<? extends NaturalNumber> ln = le;
describes a (reference to an) object ln
whose type parameter is not precisely known at compile time and in fact could be different at different points during the execution of the program. Assigning to that variable a reference to an object whose type is more precisely known doesn't matter, because (1) that doesn't change the declared type of ln
, and (2) because a differently-parameterized list could be reassigned later. (That that does not actually occur in the given code is irrelevant.)
In fact, you cannot add any element to list ln
, because you (explicitly) do not know what type of elements it accepts. You can, however, retrieve elements from it:
le.add(new EvenNumber(0));
NaturalNumber n = ln.get(0);
You have the reverse situation if you specify a lower bound:
List<? super EvenNumber> lq = le;
NaturalNumber n = lq.get(0); /* type safety error */
The situation is exactly reversed: you cannot be sure what types of objects are in list lq
, as it could be a reference to, say, a List<Object>
(though you can always do this:
Object o = lq.get(0);
.) You can, however, be certain that whatever the actual type parameter, it is consistent with adding EvenNumber
s:
lq.add(new EvenNumber(12));
Upvotes: 1
Reputation: 20450
You can't add to ln because that could potentially result in a runtime error. C# and Scala better accommodate what is known as covariance/contravariance where the generic parameter is always used as a output/input parameter. C#/Scala will let you safely down/up cast accordingly. Collections and lists use the generic parameter in both an input and output capacity, so you can't safely allow casting, and as a result those types are invariant even in C#/Scala.
Upvotes: -1
Reputation: 1129
This is a classical problem. The reason it's fobidden is because if it was allowed:
List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(50));
ln.add(new EvenNumber(46));
EvenNumber even = le.get(0); // ClassCastException
We are guaranteed by le declaration that all numbers must be even numbers. But if you're allowed to add a NaturalNumber there then this guarantee breaks.
Upvotes: 5