Home > other >  Why cannot assign value to List
Why cannot assign value to List

Time:02-03

I can't assign value to List.

I have two class like this:

public class Point<T extends Number> {
    private T x;
    private T y;
    public Point() { }
    public Point(T x, T y) {
        this.x = x;
        this.y = y;
    }
}

public class Plane<T extends Number> {
    private List<Point<? extends T>> points;
    public Plane() { }
    public Plane(List<Point<? extends T>> points) {
        this.points = points;
    }
}

Then I use List to init a value and use the List to init Plane like this:

List<Point<Float>> lp = new ArrayList<Point<Float>>();
Plane<Float> plane = new Plane<Float>(lp); 

I get a error java.util.List<Point<java.lang.Float>> can not cast to java.util.List<Point<? extends java.lang.Float>>

I found this page Difference between <? super T> and <? extends T> in Java, and that said we can use

List<? extends Number> foo3 = new ArrayList<Integer>();.

So i think Point<? extends Float> p = new Point<Float>() is the correct code

Back to my code, I think class Plane's constructor parameters witch type List<Point<? extends T>> points can receive type List<Point<Float>>, but i get that error!

Can you tell me why?

I search in google, why can't use List<someclass> to List<someclass<? extend Float>>

CodePudding user response:

You are actually close to the correct answer!!!

So i think Point<? extends Float> p = new Point() is the correct code

Yes, this code is exactly correct,and you think of them as having an inheritance relationship

It is easier to illustrate the problem if you compare Float and Number.

Float is extends Number, so Float[] is extends Number[] we call that 'Covariance'.

but Point<Float> is not extends Point<Number>, we call that generics is 'Invariance'

But the magic is Point<Float> 'extends' Point<? extends Number>(At least we can see them as having an inheritance relationship)

So, we can use Point<? extends Number> p = new Point<Float>() and Point<? extends Float> p = new Point<Float>().

In your code, you want to assign List<Point<Float>> to List<Point<? extends Float>>(cause T is Float, so List<Point<? extends T>> is List<Point<? extends Float>>)

but as generics is 'Invariance', although Point<Float> 'extends' Point<? extends Float>, but List<Point<Float>> not extends List<Point<? extends Float>>.

what you should do is change the List<Point<? extends T>> to List<? extends Point<? extends T>>

CodePudding user response:

The constructor of Plane in your call takes a List<Points<? extends Float>>, but you are passing it a List<Points<Float>>.

The relationship between Points<? extends Float> and Points<Float> is exactly like that of Number and Integer - one is the subtype of another. Compare:

// in both cases, you are assigning an instance of a subtype to a variable of a supertype
Points<? extends Float> p = new Points<Float>();
Number n = Integer.valueOf(1);

So trying to pass (or implicitly convert) a List<Points<Float>> into a List<Points<? extends Float>> is just like trying to do that with a List<Integer> and List<Number>. As you probably know already, it doesn't work at all because allowing you to do this would make it unsafe to add things into the list:

List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers; // suppose this is allowed...
numbers.add(0.1f); // I can now add a float into the integer list!

In practice though, I think the same thing cannot happen with List<Points<? extends Float>> and List<Points<Float>>, because it is not possible to create another concrete subtype of Points<? extends Float> that is also not a subtype of Points<Float>. This is mainly because Float is final. However, the Java compiler isn't designed to see this.

List<Points<Float>> floatPoints = new ArrayList<>();
List<Points<? extends Float>> extendsFloatPoints = floatPoints; // suppose this is allowed
extendsFloatPoints.add(/* whatever you can add here can be added to floatPoints */);

One way to fix the error, is as you have found, to add ? extends. In your case, this means:

private List<? extends Points<? extends T>> points;
public Plane() { }
public Plane(List<? extends Points<? extends T>> points) {
    this.points = points;
}

Note that this is in addition to the ? extends in the type parameter of Points. This is analogous to turning List<Number> numbers = new ArrayList<Integer>(); into List<? extends Number> numbers = new ArrayList<Integer>();

This essentially prevents you from adding anything into the list except nulls.

Another way to solve the problem is to create a List<Points<? extends Float>> from the very beginning and pass that into the constructor. It is totally possible to do:

List<Points<? extends Float>> lp = new ArrayList<>();

lp.add(new Points<Float>(1f, 2f));
// add more points...

Plane<Float> plane = new Plane<>(lp);

A third way is to just get rid of all the wildcard all together, and just use List<Points<T>> everywhere. I don't see much value in using them, since most of the Number subclasses are final anyway.

  • Related