Search code examples
javalower-bound

Can anyone explain this code using bounded wildcards in java?


I know my question may seem like a duplicated one but i have read many of the similar questions and answers yet I am confused specifically with lower bounds (? super) . Please consider this code .

 import java.util.*;
   public class Main {
  // just adds numbers to the List nums
   static void myadd(List<? super Number> nums){
    
    nums.add(1);
    nums.add(1.0f);
    nums.add(1.1);
    nums.add(3l);
  }
  public static void main(String[] args) {
      
      List empty= new ArrayList<>();
      myadd(empty);
      System.out.println(empty);
  }
}

Here is the Link to execute it .

According to oracle docs

a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type

Number class is the superclass of classes Double, Float, Integer, Long, and Short then why this method executes perfectly fine ? This method (myadd) should only work for objects which are direct instances of Number class or it's super type , not for it's sub classes.

I have read following answers still my doubt is not clear .

  1. Understanding upper and lower bounds on ? in Java Generics
  2. Java lower bound wildcards

If anyone can explain I will be obliged.


Solution

  • List<? super Number> means that only List<Number> (or some superclass of Number, which is Object) can be assigned to that variable (nums, in your example).

    Because any Long, Double, etc. is-a Number instance, it can be contained in a List<Number>—or, for that matter, a List<Object>. However, if you were to assign an element from the list to another variable, the variable would have to be of type Object, since the actual type of the list could be List<Object>.

    List<? extends Number> would mean that List<Number> or List<Long> or List<Double> or any other subclass of Number may be assigned to that variable.

    Because such a list is permitted to contain only a certain subclass of Number—like Long or Double—but the particular subclass is unknown, nothing can be safely added to the list. Any of its elements can be assigned to a variable of type Number, however.

    Consider the following examples:

    List<Object> objs = new ArrayList<>();
    List<? super Number> nums = objs;
    // objs is `List<Object>` so clearly this is fine:
    objs.add(Integer.valueOf(1));
    objs.add("Hello, World!");
    // We've forgotten that nums is actually the same `List<Object>`, so this won't compile:
    nums.add("Nice to meet you!"); 
    // But we remember that nums is at least a `Number` so this is okay:
    nums.add(Double.valueOf(1.0));
    // This won't compile, because `nums` could/does have a `String` in it:
    Number num = nums.get(0);
    // This is okay; nums could hold any superclass of `Number`
    Object obj = nums.get(0);
    

    Then what is the difference between both of them because ultimately i am able to add subclasses of Number in both extends & super ?

    No, this is wrong. As I said, "Because such a list is permitted to contain only a certain subclass of Number—like Long or Double—but the particular subclass is unknown, nothing can be safely added to the list." You can't safely add a subclass of Number (or anything) to a List<? extends Number>. The ? doesn't mean "any subclass"; it means "unknown subclass". It's a list of some subclass of Number that you don't know.

    List<Integer> ints = new ArrayList<>();
    List<? extends Number> alias = ints;
    // This won't compile, because you'd be polluting `ints` with a float.
    alias.add(Float.valueOf(1F));
    List<Number> anyNum = new ArrayList<>();
    // These adds are fine, anyNum can contain any subclass of Number
    anyNum.add(Integer.valueOf(1));
    anyNum.add(Float.valueOf(1F));
    // This is fine...
    alias = anyNum;
    // ...but these will fail, because we "forgot" that anyNum points to List<Number>
    alias.add(Integer.valueOf(2));
    alias.add(Float.value(2F));
    

    Please Justify what you said in 2nd paragraph of your answer you said " Because any Long, Double, etc. is-a Number instance, it can be contained in a List<Number>—or, for that matter, a List<Object>" I don't agree since Long, Double all these wrapper classes are not instances of Number class. Instead they are instances of sub-classes of Number class.

    I used some jargon there, "is-a", with which you may be unfamiliar. When one class is a subclass of another, it should be fully substitutable for the superclass. A Long or a Double can perform any operation a Number can; we say that a Double "is-a" Number. An assignment like Number x = Integer.valueOf(1) is perfectly fine because Integer is-a Number. And of course, nums.add(x) would be perfectly fine, because the type of x is Number. Would a (widening) cast from type Child to type Parent succeed? Then Child is-a Parent.

    However I agree with this line "if you were to assign an element from the list to another variable, the variable would have to be of type Object, since the actual type of the list could be List<Object>." because when i tried to assign a value from the list to a Float variable I had to typecast it to float .This raises more qestion because if List can only accept Object then why it is accepting Integer , FLoat .. in myadd() method???

    A variable of type List<?> means that the list has some type, but we don't know what it is. Nothing can be safely added to it, and if we take an element from it, all we know about the type is that it is an Object or any subclass, thus we can only safely assign it to Object. Don't think of List<?> as "a list of any type", but as "a list of unknown type."

    A variable of type List<Object> contains anything that "is-a" Object. Since any type ultimately extends Object, any type can be added to this list.

    A List<Number> contains anything that "is-a" Number. Long, Double, etc. have that "is-a" (subtype) relationship with Number, so they can be added to the list. I can define my own custom Number type and add it to the list.

    A variable of type List<? extends Number> means that the list has some type, but we only know that the type is Number or a subclass. We still can't add anything to it safely, because we don't know if it's an alias for a List<Long>, List<Double>, or what have you. But, if we take an element from it, we can assign it to Number instead of Object, because we know at least that it is-a Number.

    A variable of type List<? super Number> means that the list has some type, but we only know that the type is Number or a superclass. We can add anything that "is-a" Number (which includes subclasses). But, if we take an element from it, we don't know it is a Number; the alias could point to a List<Object>, so it might contain objects of any class. We can only safely assign its elements to variables of type Object.

    You can't use wildcards when you instantiate a generic type. That is, you can't write new ArrayList<? extends Number>. Wildcards are only used like an alias. A value with no wildcard is only aliased under a type with a wildcard, whether during assignment to a variable, passing as a parameter, or returning as a method result.

    I keep using the word "safely", because Java generics are about type-safety. If you don't suppress or ignore any type-safety warnings, you won't have any class-cast exceptions at runtime unless there is a corresponding explicit cast in your code. If you suppress or ignore type-safety warnings, you might encounter a class cast exception from a point in your source code where no cast is visible. The compiler inserts these implicit casts for you in order to support generic types.

    Back to your original example, let me rewrite it and ask you where your question lies.

    static void myadd(List<? super Number> nums) {
        /* Create Number objects */
        Number anInteger = 1;
        Number aFloat = 1F;
        Number aDouble = 1D;
        Number aLong = 1L;
        /* Add Number objects to list */
        nums.add(anInteger);
        nums.add(aFloat);
        nums.add(aDouble);
        nums.add(aLong);
    }
    

    Do you not understand that a Integer, Float, Double, and Long can be assigned to a Number? Or do you not understand that a Number can be added to a List<? super Number>?