1

l try to understand wildcard in generics ,and l have question List<? super Number > can refer to any list of object and add any object extends Number into this list but l can't add into it Object not extends number(String) but why l can do this in this code without any compile error or exception at run-time (referring to list contain String object)

edit:what l want to understand that generics provide compile time safe and this not achieved in my example

List <? super Object> objectList = new ArrayList<>();
objectList.add("str1");

List<? super Number> numberList = objectList;
numberList.add(1);

objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
    System.out.println(objectList.get(i) + "");
} 
Hanaa Aldaly
  • 79
  • 1
  • 8
  • And I'm guessing you'll have issues if you do something like `numberList.get(i) + ""`. You are using the `objectList` in the loop, so I wouldn't expect a problem there. – forgivenson Oct 31 '18 at 18:35
  • Gah I can't think of the proper name for this, but effectively the direction of the bounding is important here. But the type "checking" is done on retrieval, not when you're inserting into the object list (which would pass the `Object` type check anyhow). – Rogue Oct 31 '18 at 18:36
  • @Rogue [PECS](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super)? – takendarkk Oct 31 '18 at 18:37
  • Actually `super Number` = `extends Object` so it is almost equal to `super Object`. – BladeMight Oct 31 '18 at 18:37
  • no l don't have issues if l loop on numberList and print numberList.get(i) + "" it give the same result – Hanaa Aldaly Oct 31 '18 at 18:38
  • @csmckelvey not quite, that's more with the wildcard bounding. It's killing me a little bit that I can't think of it. – Rogue Oct 31 '18 at 18:38
  • "why l can do this in this code without any compile error or exception at run-time (referring to list contain String object)" why would you expect error here? – Pshemo Oct 31 '18 at 18:47
  • List refer to string Object this is not type safe and generics provide compile time type safe so l expect to generate compile error – Hanaa Aldaly Oct 31 '18 at 18:52
  • 2
    `List super SomeType>` only allows you to ***add*** data (see [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/q/2723397)). So why would it be not safe to let `List super Number>` to handle `List super Object>`? If we know that `List super Object>` hold lists of Objects then adding Integer to it shouldn't be a problem because held `List` (or List - if was possible) wold be declared to hold Object (which includes its subtypes). – Pshemo Oct 31 '18 at 18:55
  • Here is, I think, a very good explanation: https://stackoverflow.com/questions/3847162/java-generics-super-keyword –  Oct 31 '18 at 19:00
  • 1
    Not an exact duplicate, but this question is very closely related: [Generics ` super A>` doesn't allow supertypes of `A` to be added to the list](https://stackoverflow.com/questions/44008369/generics-super-a-doesnt-allow-supertypes-of-a-to-be-added-to-the-list). – Daniel Pryden Oct 31 '18 at 19:34

4 Answers4

4

You have two different kinds of polymorphism that are interacting here, in a confusing way.

The key to understanding this is that, in addition to the parametric polymorphism (that is, generics), you also have subtype polymorphism, that is, the classic object-oriented "is-a" relationship.

In Java, all objects are subtypes of Object. So a container that can contain Object values can contain any value.

If we rewrite all the generic bounds as just <Object> then the code works the same way, and obviously so:

List<Object> objectList = new ArrayList<>();
objectList.add("str1");

List<Object> numberList = objectList;
numberList.add(1);

objectList.add("str2");
for (int i = 0; i < objectList.size(); i++) {
    System.out.println(objectList.get(i) + "");
}

Specifically, objectList.get(i) + "" gets evaluated into something that calls objectList.get(i).toString(), and since toString() is a method of Object, that will work regardless of the type of objects in objectList.

What won't work is this:

Number number = numberList.get(i);  // error!

This is because, despite the misleading name, numberList is not guaranteed to contain only Number objects, and might in fact not contain any Number objects at all!

Let's walk through and see why this must be so.

First we create a list of objects:

List<? super Object> objectList = new ArrayList<>();

What does that type mean? The type List<? super Object> means "a list of objects of some type, I can't tell you what type, but I do know that whatever type it is is either Object or a supertype of Object". We already know that Object is the root of the subtype hierarchy, so this is effectively the same as List<Object>: that is, this object can only contain Object objects.

But... that's not quite right. The list can only contain Object objects, but an Object object can be anything! The actual objects at runtype can be of any type that is a subtype of Object (so, anything other than a primitive), but by putting them into this list, you're losing the ability to tell what kind of objects they are anymore -- they could be anything. That's OK for what the rest of this program does, though, because all it needs to be able to do is call toString() on the objects, and it can do that because they all extend Object.

Now let's look at the other variable declaration:

List<? super Number> numberList = objectList;

Again, what does the type List<? super Number> mean? Crucially, it means "a list of objects of some type, I can't tell you what type it is, but I do know that whatever type it is, it's either Number or some supertype of Number". Well, on the left hand we have a list of "Number or some supertype of Number" and on the right side we have a list of Object -- clearly Object is a supertype of Number and so this list is a list of Object. Everything type checks (and, contrary to my initial comment, without any warnings).

So the question becomes: why can a List<? super Number> contain a String? Because a List<? super Number> can be just a List<Object>, and a List<Object> can contain a String because String is-a Object.

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
  • 3
    @aka-one: Because `List super Number>` **can be** just a `List`, but it could also be a `List`. The compiler doesn't know which, so it has to be conservative. If it was a `List` then adding an `Object` to it would break the list, and thus you get a type error. – Daniel Pryden Oct 31 '18 at 18:59
  • yes this what l mean List super Number> numberList = objectList Now the list contains a mix of String + Number although numberList.add("str") not allowed – Hanaa Aldaly Oct 31 '18 at 19:06
  • 4
    @aka-one: There is nothing prohibiting a `List super Number>` from containing Strings. What is prohibited is *adding* Strings to a list through a `List super Number>` variable. – user2357112 Oct 31 '18 at 19:06
  • @aka-one: Like I said, there are two types of polymorphism here, and they interact in a confusing way. From the generics perspective, the list does not contain "a mix" of types: it contains exactly `Object` and only `Object`. Independently of that mechanism, those `Object` values could be a mix of subtypes of `Object`, but that would be equally true if you just had several different local variables all declared as type `Object`: it has nothing to do with `List` or with generics at all. – Daniel Pryden Oct 31 '18 at 19:08
  • @user2357112 That's the question: why doesn't allow String, since super Number> means Number, Serializable or Object? –  Oct 31 '18 at 19:09
  • 3
    @aka-one: I think you misunderstand what the `?` means in a generic type. It doesn't mean "any of these types", it means "exactly one of these types, but I won't tell you which". It *could* be a list of `Number`, or of `Serializable`, or of `Object`. Essentially, `List super Number>` means "a list of some type, I couldn't tell you what type exactly, but all I know is that it's safe to *add* `Number` to it". It tells you nothing about what you would get if you were to take something *out* of the list. That what the "Producer Extends, Consumer Super" rule of thumb means. – Daniel Pryden Oct 31 '18 at 19:12
  • 1
    It is worth to remember that `any` != `all`. `List extends Cat> list` means that list can hold *any* of `new List()` `new List()` `new List()`, but at a time it can hold only *one* of them (and placing Cat into such lists shouldnt be a problem). – Pshemo Oct 31 '18 at 19:14
  • @DanielPryden if (List super Number> contain a String? Because a List super Number> can be just a List, and a List can contain a String because String is-a Object.) why l can't do this NumberList.add("string"); – Hanaa Aldaly Oct 31 '18 at 19:17
  • @HanaaAldaly: Just because a list could **contain** a `String` doesn't mean that it's safe to **add** a `String` to it. It's safe to add a `String` to a `List`, but not to a `List`. This ambiguity is precisely why the `? super` syntax exists. If you declared another list as `List super String>`, then you could add strings into that one. – Daniel Pryden Oct 31 '18 at 19:19
  • @DanielPryden but generics is compile time type safe and now numberList can refer to object not subclass from Number :(( – Hanaa Aldaly Oct 31 '18 at 19:24
  • @HanaaAldaly: I don't understand that last comment at all. Are you saying you still don't understand my explanation of what's happening, or that you're disappointed that Java behaves the way it does? – Daniel Pryden Oct 31 '18 at 19:29
  • 1
    @HanaaAldaly Maybe this will help a little. Compiler doesn't *know* what *kind of list* exactly `numberList` will hold. Just like in case `void someMethod(List super Number> numberList){ numberList.add(...);}` it can't know if we pass to it List or List. To make that method safe it needs to allow only instructions which would be valid for all of those types. If user will pass `ArrayList` then indeed adding String to it wouldn't cause any problem to that list, BUT if user will pass as argument `List` than adding string to it wouldn't be safe. – Pshemo Oct 31 '18 at 19:32
  • @HanaaAldaly That is why `numberList.add("someString")` can't be allowed. Only safe type which we can use with `someMethod.add(...)` which would be supported by all acceptable arguments (List and List) is Number itself (which includes its subtypes like in your case Integer). – Pshemo Oct 31 '18 at 19:32
  • @DanielPryden l understand you but what l mean super Number> mean l can add only SubClass of Numer add can't add String to this list and numberList = objectList; now numberList contain objects not subclass from Number – Hanaa Aldaly Oct 31 '18 at 19:34
2

A reference of type List<? super Number> can refer to either List<Object> or List<Number>. Any operation via this reference need to work with either of these types. You couldn't add a string via the List<? super Number> reference because the operation would only work with one of the possible object types, but you can via the List<? super Object> reference.

A List<? super Object> can only refer to a List<Object>. A List<? super Number> can refer to either a List<Number> or a List<Object>. This is a more general type, which is why the assignment is allowed.

fgb
  • 18,439
  • 2
  • 38
  • 52
1

This is lower bounded wildcard feature in java generics.

As per Java documentation, a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.

Initially you are creating a list with type of Object or super type of Object. As you know in java every class has Object as a superclass. So we can String class as an instance of Object. As your list is allowing type of Object, it can allow type of String also.

Same is the case with List<? super Number> numberList = objectList; but you can't do viceversa.

Refer to get more understanding about Lower bound wildcards: Java lower bound wildcards https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html

Pshemo
  • 122,468
  • 25
  • 185
  • 269
1

When the compiler sees:

List<? super Number> numberList = objectList;

It first captures the wildcard. The generic type then becomes Y = X>Number (meaning a concrete supertype of Number). So we have:

List<Y> numberList = objectList //with type of List<Object>;

Then the compiler determines that Y can be replaced with Object. Therefore, the types are identical and numberList is allowed to point to the same object as objectList.

Then the generated bytecode is passed to the runtime system for execution. As far as the runtime system is concerned, both lists have the type of java.util.ArrayList due to type erasure. Therefore, no runtime exception will be raised when you put strings or other objects in this container.

But I also feel something doesn't feel quite right here. To rephrase your question:

What the compiler can do to prevent this situation?

Note that the compiler MUST NOT complain during assignment because:

The Safe Instantiation Principle: Instantiating a parametric class with types that meet the declared constraints on the parameters should not cause an error.

I think this principle applies to assignments too. The assignment does not break any language rule, so the compiler must not raise an error.

So the only place left to save the programmer from disaster is during the add operation. But what can the compiler do there? If it disallows add operation on objectList because of the assignment, that will break other language rules. If it augments add to support adding objects to numberList, that will also violate some other language rules.

I can't think of any straightforward and easy solution that doesn't break a whole lot of things for fixing something that might not even be a problem and the programmer is certainly in a good position to decide about.

The type checker is meant to help the programmer not replace her. Another example of its imperfection:

public static void main(String[] args) {
    Object m = args;
    String[] m2 = m; //complains, despite m2 definitely being an String[]
}

P.S: I found the above example on SO but unfortunately, I lost the link!

jrook
  • 3,459
  • 1
  • 16
  • 33