Is List<Double> a subtype of List<? extends Number> and why?
Asked Answered
B

4

12

Here is what I know:

  1. Double is a subtype of Number and List<Double> is not a subtype of List<Number>.
  2. List<Dog> is not a subtype of List<Animal> because you can add Cat to List<Animal> but you can't do that with List<Dog>.
  3. List<? extends Number> means this list can store variables of type Number and variables of subtype of Number. List<Double> means this list can store variables of type Double.

Please correct me if anything above is wrong and then Is List<Double> a subtype of List<? extends Number> and why?

Brownie answered 28/5, 2015 at 5:50 Comment(2)
Item 3 is true, but you can't store both Double and Integer in the same List<? extends Number> list. Both List<Double> and List<Integer> are List<? extends Number>, but List<? extends Number> is not the same as List<Number>.Grandpa
I think it's clear. Dog presumably extends Animal.Grandpa
H
6

There are a few points that should also be added

  1. At runtime List<Double>, List<? extends Number>, List<?> and List<Object> are all identical. The generic parameter is not compiled at all. All the magic with generics is all compile time fun. This also means that if you have a List that is empty you have no idea what the generic parameter is!

  2. Try not to think of the generic parameter as a "Subtype", the generic parameter really means "the class uses a generic parameter" so in this case "the list uses a Number". A good example of this is the HashMap source, if you have a look into the inner workings of it, it is actually storing an array of Entry, and the entries all have keys and values stored on them. When you look at more complex uses of generics you occasionally see this sort of use.

    In the situation of a List the generic parameter means that the list stores that type of object, it could be that the object never stores an object of the type of the generic parameter at all! Like this:

    public class DummyIterator<O> implements Iterator<O>{
        public boolean hasNext() {
            return false;
        }
    
        public O next() {
            return null;
        }
    }
    
  3. What does List<? extends Number> actually mean? Well it is pretty much the same as List<Number> for most uses. Keep in mind though that by saying ? you are pretty much saying 'I don't care about the type' which shows itself in this situation:

    List<Double> doubles = new ArrayList<Double>();
    List<? extends Number> numbers = doubles;
    numbers.add(new Double(1));  //COMPILE ERROR
    Number num = numbers.get(0);
    

    So we can't add a Double to a <? extends Number>. But for this example:

    List<Double> doubles = new ArrayList<Double>();
    List<Number> numbers = doubles; //COMPILE ERROR
    numbers.add(new Integer(1));
    Number num = numbers.get(0);
    

    You can't assign the List<Double> to a List<Number> which makes sense as you are specifically telling it, that lists use only Number types

  4. So where should you use a ?? well really anywhere you could say "I don't care about the generic parameter" so for instance:

    boolean equalListSizes(List<?> list1, List<?> list2) {
      return list1.size() == list2.size();
    }
    

    You would use the ? extends Number type of format only where you are not modifying the object using the generic parameter. so for instance:

      Number firstItem(List<? extends Number> list1) {
        return list1.get(0);
      }
    
  5. Instead of using the ? and ? extends Number formats try using generics on the class / method instead, in most cases it makes your code more readable as well!:

    <T extends Number> T firstItem(List<T> list1) {
      return list1.get(0);
    }
    

    Class:

    class Animal{}
    class Dog extends Animal{}
    class AnimalHouse<A extends Animal> {
        List<A> animalsInside = new ArrayList<A>(); 
        void enterHouse(A animal){
            animalsInside.add(A);
        }
    
        A leaveHouse() {
            return animalsInside.remove(0);
        }
    }
    AnimalHouse<Dog> ah = new AnimalHouse<Dog>();
    ah.enterHouse(new Dog());
    Dog rufus = ah.leaveHouse();
    
  6. As a bonus thought around generics you can also parameterise methods to return a particular class. A good example of this is the any() method in junit and the empty list collection:

    Dog rufus = Matchers.<Dog>any();
    List<Dog> dogs = Collections.<Dog>emptyList();
    

    This syntax allows you to specify the return type of an object. Sometimes quite useful to know (makes some casting redundant)!

Hoplite answered 29/5, 2015 at 1:12 Comment(3)
Could you explain more on your second point? 1."...so in this case "the list uses a Number". " What's the meaning of uses here? Do you mean "store"? 2."...the list stores that type of object..." and "...it could be that the object never stores an object of the type of the generic parameter..." This long sentence is really too complicated for me to understand(I'm not a native speaker)Brownie
@kahofanfan: edited, I hope this clarifies what I mean for you.Hoplite
Nice method names enterHouse() and leaveHouse()Byrnes
G
11

All your items are correct.

  1. Double is a subtype of Number and List<Double> is not a subtype of List<Number>.

  2. List<Dog> is not a subtype of List<Animal> because you can add Cat to List<Animal> but you can't do that with List<Dog>.

That's correct. Generics aren't covariant (but arrays are!). Here's some follow up reading: Why are arrays covariant but generics are invariant?

  1. List<? extends Number> means this list can store variables of type Number and variables of subtype of Number. List<Double> means this list can store variables of type Double.

This is true, but there's an important difference between List<Number> and List<? extends Number>. You can think of List<? extends Number> as a list of a specific Number-subtype (that is one of List<Double>, List<Integer>, List<Long>, ...) and a List<Number> as a list that can potentially contain a mix of Double, Integer, ...

As for your final question:

Is List<Double> a subtype of List<? extends Number>...

Yes, you can have for instance

List<Double> doubles = new ArrayList<>();
List<? extends Number> numbers = doubles;

... and why?

This is just the way subtyping is defined.

As for the motivation, suppose you have a method that accepts a list of numbers. If you let the parameter have the type List<Number> you won't be able to pass a List<Double> to it. (Your second item in your question explains why!) Instead, you can let the parameter have type List<? extends Number>. Since List<Double> is a subtype of List<? extends Number> it will work out.

Grandpa answered 28/5, 2015 at 5:59 Comment(17)
But List<? extends Number> may store variables of type Int while List<Double> can't. And we know that a subtype should has all functions that its supertype does. So I'm confused.Brownie
That's like saying an Animal can bark while Cat can't.Grandpa
The other thing to consider is: numbers.add(new Double(1.2)); and numbers.add(new Integer(1)); won't compile but Number num = numbers.get(0); will return something that extends Number. Generally try and steer clear of <? extends X> you are better to use a class or use a <T> style. eg. public <T> void addToList(List<T> doubles, T value){ doubles.add(value); }Hoplite
Can you really consider List<Double> is a subtype of List<? extends Number> when you cannot overload a method with both (cannot have both sampleMethod(List<Double>) and sampleMethod(List<? extends Number>) in the same class) since they are equivalent at runtime (erasure is, well... erased by compilation)?Limburger
@Chop, I don't think method overloading has to do with subtyping (at least not in the context of this question).Grandpa
Another point: you cannot determine inheritance on erasure using instanceof. I always understood erasure as compiler and developer instructions on how to use a specific object or method rather than real typing. Your point of Animal barking while Cat can't is wrong: if Cat extends Animal and Animal barks, then Cat barks too. To me, you can only say that the lists meet some requirements of inheritance but in the end, they are the same type with different restrictions.Limburger
@Grandpa You can have sampleMethod(Animal) and sampleMethod(Cat) in a same class because they are different types. Lists are just Lists, no matter their erasure.Limburger
I meant that an Animal can bark if it's a Dog (sorry if that wasn't clear). In that case I think we both agree that a Cat can't bark :-)Grandpa
While the OP asked why you could add integers to List<? extends Number> (the Animal) and not to List<Double> (the Cat). I fail to see the relevance. (While I may seem aggressive, I actually find this subject and debate quite interesting).Limburger
Ah now I see. Good point. Very related to OP's previous question too.Grandpa
@Limburger We can have subtyping without inheritance. Inheritance is only one way to have a subtype in the Java type system. List<Double> does not "inherit" from List<? extends Number> but List<Double> is a subtype of List<? extends Number>.Cheerless
In Java, subtyping basically means inheritance. There's no subtyping involved; there's just an implicit conversion from List<Double> to List<? extends Number>.Catalogue
"But List<? extends Number> may store variables of type Int " - no, it may store List<Integer>; that's not the same thing.Woodwork
@Woodwork It could also store List<Number>, to which you could add Integer instances.Limburger
@Cheerless Thank you for pushing me. I just found a page on Oracle docs which supports this answer.Limburger
@Catalogue No, that's not true. See 4.10.2 and 4.5.1.Cheerless
@Radiodef: Oi. So "subtype" means something different to Java than it does to every other language i use...? I'm starting to remember why Java irks me. :PCatalogue
G
6

At runtime, List<T> and List<U> are identical to List (1).

However, this will change with the introduction of value types(expected to make it in the JDK 9 or JDK 10 release, not sooner than mid 2016). List<T> will not be the same as List<U> anymore due to numerous constraints explained here by Brian Goetz: http://cr.openjdk.java.net/~briangoetz/valhalla/specialization.html

(1) - T and U types are different in the previous statements

Guess answered 28/5, 2015 at 6:3 Comment(7)
This is by no means settled. Everything is still up in the air. As Brian writes: This is an informal sketch of proposed enhancements to the Java LanguageGrandpa
If they change their minds, I will edit/delete my post. It would be nice if we had a chance to read his opinion about this question.Guess
Still though, I don't see how your answer relates to the question. OP is talking about subtyping (which is handled statically by the compiler) and your answer talks about runtime.Grandpa
Inheritance exists at runtime too. What is wrong with a different point of view?Guess
The OP asks if his three items are correct, and then asks Is List<Double> a subtype of List<? extends Number> and why? I fail to see how you address any of this in your answer. (It's not a matter of having a different point of view.)Grandpa
Whatever happens with value types, I can guarantee you that it will be backwards compatible and the answer to the OP's question will be the same after they are introduced.Grandpa
I am not so sure about the backwards compatibility this time ... let me have a different opinion on that, will you? :)Guess
H
6

There are a few points that should also be added

  1. At runtime List<Double>, List<? extends Number>, List<?> and List<Object> are all identical. The generic parameter is not compiled at all. All the magic with generics is all compile time fun. This also means that if you have a List that is empty you have no idea what the generic parameter is!

  2. Try not to think of the generic parameter as a "Subtype", the generic parameter really means "the class uses a generic parameter" so in this case "the list uses a Number". A good example of this is the HashMap source, if you have a look into the inner workings of it, it is actually storing an array of Entry, and the entries all have keys and values stored on them. When you look at more complex uses of generics you occasionally see this sort of use.

    In the situation of a List the generic parameter means that the list stores that type of object, it could be that the object never stores an object of the type of the generic parameter at all! Like this:

    public class DummyIterator<O> implements Iterator<O>{
        public boolean hasNext() {
            return false;
        }
    
        public O next() {
            return null;
        }
    }
    
  3. What does List<? extends Number> actually mean? Well it is pretty much the same as List<Number> for most uses. Keep in mind though that by saying ? you are pretty much saying 'I don't care about the type' which shows itself in this situation:

    List<Double> doubles = new ArrayList<Double>();
    List<? extends Number> numbers = doubles;
    numbers.add(new Double(1));  //COMPILE ERROR
    Number num = numbers.get(0);
    

    So we can't add a Double to a <? extends Number>. But for this example:

    List<Double> doubles = new ArrayList<Double>();
    List<Number> numbers = doubles; //COMPILE ERROR
    numbers.add(new Integer(1));
    Number num = numbers.get(0);
    

    You can't assign the List<Double> to a List<Number> which makes sense as you are specifically telling it, that lists use only Number types

  4. So where should you use a ?? well really anywhere you could say "I don't care about the generic parameter" so for instance:

    boolean equalListSizes(List<?> list1, List<?> list2) {
      return list1.size() == list2.size();
    }
    

    You would use the ? extends Number type of format only where you are not modifying the object using the generic parameter. so for instance:

      Number firstItem(List<? extends Number> list1) {
        return list1.get(0);
      }
    
  5. Instead of using the ? and ? extends Number formats try using generics on the class / method instead, in most cases it makes your code more readable as well!:

    <T extends Number> T firstItem(List<T> list1) {
      return list1.get(0);
    }
    

    Class:

    class Animal{}
    class Dog extends Animal{}
    class AnimalHouse<A extends Animal> {
        List<A> animalsInside = new ArrayList<A>(); 
        void enterHouse(A animal){
            animalsInside.add(A);
        }
    
        A leaveHouse() {
            return animalsInside.remove(0);
        }
    }
    AnimalHouse<Dog> ah = new AnimalHouse<Dog>();
    ah.enterHouse(new Dog());
    Dog rufus = ah.leaveHouse();
    
  6. As a bonus thought around generics you can also parameterise methods to return a particular class. A good example of this is the any() method in junit and the empty list collection:

    Dog rufus = Matchers.<Dog>any();
    List<Dog> dogs = Collections.<Dog>emptyList();
    

    This syntax allows you to specify the return type of an object. Sometimes quite useful to know (makes some casting redundant)!

Hoplite answered 29/5, 2015 at 1:12 Comment(3)
Could you explain more on your second point? 1."...so in this case "the list uses a Number". " What's the meaning of uses here? Do you mean "store"? 2."...the list stores that type of object..." and "...it could be that the object never stores an object of the type of the generic parameter..." This long sentence is really too complicated for me to understand(I'm not a native speaker)Brownie
@kahofanfan: edited, I hope this clarifies what I mean for you.Hoplite
Nice method names enterHouse() and leaveHouse()Byrnes
S
3

It helped me to see generics as constraints or contracts, not as types with subtypes. So a variable List<? extends Number> var says: var is a list of some unknown type ?, which is constrained to be a subtype of Number.

List<Number> listN;
List<Double> listD;
List<? extends Number> listX;
...
Number n1 = ...;
Double d1 = ...;
...
listN.add(n1); // OK n1 is a Number
listN.add(d1); // OK d1 is a Double, which is a Number
listD.add(n1); // compile error, n1 is not a Double
listD.add(d1); // OK
listX.add(n1); // compile error, because the exact type of list is not known! (prevents putting a Dog in a Cat list)
listX.add(d1); // compile error, same cause

So when you can't even put a Number into a List<? extends Number>, whats the purpose of such a list? It allows you to work with lists of which the exact type does not matter for the task at hand:

// instead of several exactly typed methods...
int count(List<Number> numberList) {...}
int count(List<Object> objectList) {...}
// ...etc. you can have one with a degree of freedom:
int count(List<?> anyList) {...} // don't need to know the exact type of list

// instead of this...
Number sum(List<Number> numberList) {...}
Number sum(List<Double> doubleList) {...}
Number sum(List<Integer> integerList){...}
// you can do this, with a little less freedom in the ?
Number sum(List<? extends Number> list) {
  // the only thing we need to know about the list's type is that it is some number type
  ...
  Number ni = list.get(i);
  ...
}

Using wildcards ? extends X allows to relax rigid contracts to weaker conditions.

Using a named type parameter, you can establish constraints on allowed types between several variables:

// works for any type T of list, whatever T is
// T is the same in the argument and in the return
<T> T pickObject(List<T> list, int index) {
  return list.get(index);
}

// works for any type T of list, if T is a Number type
// T is the same in the argument and in the return
<T extends Number> T pickNumber(List<T> list, int index) {
  return list.get(index);
}
...
List<Number> list;
Number n = pickNumber(list);
Sibling answered 28/5, 2015 at 6:59 Comment(1)
So I am not the only one thinking this. I was thinking of writing something like this. Thank you.Limburger

© 2022 - 2024 — McMap. All rights reserved.