Reputation: 9102
I went through these topics
However, I still seem to be kind of lost with super
keyword:
When we declare a collection like that:
List<? super Number> list = null;
list.add(new Integer(0)); // this compiles
list.add(new Object()); // this doesn't compile
shouldn't it be the opposite - we have a list that contains some objects (of unknown type) which are parents of Number
. So Object
should fit (since it is the parent of Number
), and Integer
shouldn't. The opposite is the case for some reason.
Provided we have the following code
static void test(List<? super Number> param) {
param.add(new Integer(2));
}
public static void main(String[] args) {
List<String> sList = new ArrayList<String>();
test(sList); // will never compile, however...
}
It is impossible to compile the above code (and my sanity suggests that this is the right behaviour), but the basic logic could prove the opposite:
String is Object, Object is superclass of Number. So String should work.
I know this is crazy but isn't this the reason why they didn't allow <S super T>
constructs? If yes, then why <? super T>
is allowed?
Could someone help me restore the missing part of this logic chain?
Upvotes: 83
Views: 48219
Reputation: 71
List<? super Number>
means that the reference type of the variable suggests we have a list of Numbers, Objects or Serializables.
The reason you can't add an Object, is because the compiler does not know WHICH of these classes are in the generic definition of the actual instantiated object, so it only allows you to pass Number or subtypes of Number, like Double, Integer and so on.
Let's say we have a method that returns a List<? super Number>
. The creation of the object inside the method is encapsulated from our view, we just can't say if it is something like this:
List<? super Number> returnValue = new LinkedList<Object>();
or
List<? super Number> returnValue = new ArrayList<Number>();
So, the generic type could be Object or Number. In both cases, we would be allowed to add Number, but only in one case we would be allowed to add Object.
You have to distinguish between the reference type and the actual object type in this situation.
Upvotes: 7
Reputation: 120848
There are two angles here: what you can put into a collection and what you can get from a collection, when bounded types are involved.
Let's look at the ? extends Number
case first. When a collection with such bounds is defined, what we know is that : every element will have an upper bound as Number
. We don't know the exact type (might be an Integer/Long/etc
), but we do know, for sure, that its upper bound is Number
.
So reading from such a collection gets us a Number
. This is the only guaranteed type we can get from it.
Writing to such a collection is prohibited. But why? Didn't I say that while we read - we will always get a Number
, so why prohibit writing to it? The situation is slightly more involved here:
List<Integer> ints = ....;
List<? extends Number> numbers = ints;
numbers.add(12D); // add a double in here
If addition would have been allowed into numbers
, you could have effectively added a Double
in a List of Integers
.
Now to your example:
List<? super Number> list = null;
list.add(new Integer(0));
list.add(new Object());
We know about list
that it contains a certain supertype of Number
, for example Object
.
Reading from such a list would get us a certain type X
, where X
would be a parent of Number
. So what would that be? You can't really know. It could be a theoretical MyNumber extends Number
, or much simpler: an Object
. Since you can't know for sure, the only safe thing to read from that would be the super-type of everything - Object
.
What is a bit weird may be :
List<? super String> list = ...;
String s = list.get(0); // fails, compiler does not care that String is final
Writing to it is slightly more complicated, but only slightly. Remember what we know is inside that list
: it's a type that Number
extends/implements (if it were an interface), so you can always assign a subtype (or Number
itself) to that supertype.
Some type X
/ \
|
Number
/ \
|
Some type Y that we an put in here
Upvotes: 4
Reputation: 2171
May I give a very simple Example.
public void add(List<? super Number> list) {
}
this will allow these calls
add(new LinkedList<Number>());
and everything above Number like
add(new LinkedList<Object>());
but nothing below the hierarchy so not
add(new LinkedList<Double>());
or
add(new LinkedList<Integer>());
So since its not clear for the program to know whether you give a List with Number or Object the compiler cannot allow you to add anything above Number to it.
For example a List would not accept an Object in spite of Object who would accept a Number. But since this is not clear the only valid input would be Number and its sub types.
Upvotes: 3
Reputation: 8289
I didn't get it for a while. Many of the answers here, and the other questions show specifically when and where certain usages are errors, but not so much why.
This is how I finally got it. If I have a function that adds Number
s to a List
, I might want to add them of type MySuperEfficientNumber
which is my own custom class that implements Number
(but is not a subclass of Integer
). Now the caller might not know anything about MySuperEfficientNumber
, but as long as they know to treat the elements added to the list as nothing more specific than Number
, they'll be fine.
If I declared my method as:
public static void addNumbersToList(List<? extends Number> numbers)
Then the caller could pass in a List<Integer>
. If my method added a MySuperEfficientNumber
to the end of numbers
, then the caller would no longer have a List
of Integer
s and the following code wouldn't work:
List<Integer> numbers = new ArrayList<Integer>();
addNumbersToList(numbers);
// The following would return a MySuperEfficientNumber not an Integer
Integer i = numbers.get(numbers.size()-1)
Obviously this can't work. And the error would be inside the addNumbersToList
method. You'd get something like:
The method add... is not applicable for the arguments (MySuperEfficientNumber)
Because numbers
could be any specific kind of Number
, not necessarily something that MySuperEfficientNumber
is compatible with. If I flipped the declaration around to use super
, the method would compile without error, but the caller's code would fail with:
The method addNumbersToList(List<? super Number>)... is not applicable for the arguments (List<Integer>)
Because my method is saying, "Don't think that your List
can be of anything more specific than Number
. I might add all sorts of weird Number
s to the list, you'll just have to deal with it. If you want to think of them as something even more general than Number
-- like Object
-- that's fine, I guarantee they'll be at least Number
s, but you can treat them more generally if you want."
Whereas extends
is saying, "I don't really care what kind of List
you give me, as long as each element is at least a Number
. It can be any kind of Number
, even your own weird, custom, made-up Number
s. As long as they implement that interface, we're good. I'm not going to be adding anything to your list since I don't know what actual concrete type you're using there."
Upvotes: 12
Reputation: 5241
List<? super Number>
is such a List<AncestorOfNumber>
where we can implicitely cast each Number
to its super type AncestorOfNumber
.
Consider this: What generic type needs to be ????
in the following example?
InputStream mystream = ...;
void addTo(List<????> lsb) {
lsb.add(new BufferedInputStream(mystream));
}
List<BufferedInputStream> lb = new ArrayList<>();
List<InputStream> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();
...
{ addTo(lb); addTo(li); addTo(lo); }
The answer: ????
is anything to which we can cast BufferedInputStream
, which is that very same or one of its ancestors: ? super BufferedInputStream
Upvotes: 2
Reputation: 93157
For the first part List<Number>
fits in List<? super Number>
but you can't add an Object
to a List<Number>
. That's why you can't add an Object
to List<? super Number>
.
On the other hand you can add every subclass of Number
(Number
included) to your list.
For the second part, String
is an Object
, but String
isn't a superclass of Number
.
If it worked like this, as every class is a subclass of Object
, super
would have no meaning.
List<? super Number>
:List<Object>
List<Object>
will workObject
fits in <? super Number>
Number
to a List<Object>
String
in it the only thing you're sure of is that you can add any subclass of Number
.List<Number>
:
List<Number>
will workNumber
fits in <? super Number>
Number
to a List<Number>
List<Integer>
(or any subclass of Number
):
List<Integer>
won't workNumber
so it is exactly what we want to avoidInteger
fits in a Number
you wouldn't be abble to add any subclass of Number
in a List<Integer>
(for example a Float
)super
doesn't mean a subclass.List<String>
(or any class not extending Number
nor in the "super hierarchy" of Number
(ie. Number
and Object
) :
List<String>
won't workString
doesn't fit in Number
"super hierarchy"String
fits in Object
(which is a super class of Number
) you woudln't be sure to be able to add a Number
to a List
that contain any subclass from one of the super classes of Number
)super
doesn't mean any subclass of one of the super classes, it only means one of the super classes.How does it work ?
You could say that as long as you can add any subclass of Number
with your typed List
, it respects the super
keyword.
Upvotes: 26
Reputation: 383726
The bounded wildcard in List<? super Number>
can capture Number
and any of its supertypes. Since Number extends Object implements Serializable
, this means that the only types that are currently capture-convertible by List<? super Number>
are:
List<Number>
List<Object>
List<Serializable>
Note that you can add(Integer.valueOf(0))
to any of the above types. however, you CAN'T add(new Object())
to a List<Number>
or a List<Serializable>
, since that violates the generic type safety rule.
Hence it is NOT true that you can add
any supertype of Number
to a List<? super Number>
; that's simply not how bounded wildcard and capture conversion work. You don't declare a List<? super Number>
because you may want to add an Object
to it (you can't!); you do because you want to add Number
objects to it (i.e. it's a "consumer" of Number
), and simply a List<Number>
is too restrictive.
extends
, consumer-super
new Integer(0)
vs valueOf
, etcUpvotes: 106