Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why cannot assign value to List with 'general wildcard'

Tags:

java

generics

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>>

like image 625
alalalala Avatar asked Oct 30 '25 23:10

alalalala


1 Answers

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.

like image 172
Sweeper Avatar answered Nov 01 '25 13:11

Sweeper