So the function below is supposed to be a function that takes a Car
object and adds it to a Col<>
object.
void insertCar(List<? super Car> c, Car x) {
c.add(x)
}
The question asked whether if adding a variable n
of type Nissan
object to a variable c
of type List<Car>
would work (ie. insertCar(c, n)
)
The answer is yes but I'm not sure why. I thought adding subtypes of an object Car
would not be possible because of the use of super
. That it would only take types of type Car
or any supertype of Car
.
Anyone able to me understand?
EDIT Is it that...
I wouldn't be able to add a Nissan if the List<>
itself was of some other subtype being passed in? For example, if List<? super Car>
was actually List<? super Ford>
There seems to be conflicting answers below but this is a question provided for exam review so pretty sure the question and answer provided are correct. It's just my understanding of it is what I'm not sure about.
The question asked whether if adding a variable
n
of typeNissan
object to a variablec
of typeList<Car>
would work (ie.insertCar(c, n)
)
To put your words to code, I believe this is what you are talking about:
public class GenericCheck {
static void insertCar(List<? super Car> c, Car x) {
c.add(x);
}
public static void main(String[] args) {
List<Car> c = new ArrayList<>();
Nissan n = new Nissan();
insertCar(c, n);
}
}
class Car {}
class Nissan extends Car {}
Yes. this is fine, but there are 3 type checks needed to be done:
List<Car>
a valid 1st argument for insertCar
's List<? super Car>
? Yes, it is a subtype, see here for example.Nissan
a valid 2nd argument for insertCar
's Car
? Yes, it is a subtype.c.add(x)
valid? Yes, the argument x
must be of type Car
or a supertype of Car
as defined by List<? super Car> c
, and it is so by the definition Car x
.So why is it confusing? Because you pass to the second argument a Nissan
type, and think that now point 3 will break because x
is not of the type Car
or a supertype of Car
(it is a subtype). What happens is that Nissan
is being upcasted to a Car
, which is then a viable argument.
Remember that to know if the call c.add(x)
is valid, all you have to do is look the definitions of c
and x
(in the method's arguments list). This ensures that as long as the method is called with valid arguments, the call c.add(x)
is valid. This check is unrelated to the (two) type checks when calling insertCar
. Those are the 3 checks we performed above.
Edit: So why does insertCar(List<? super Nissan> c, Car x)
not work?
Because of type erasure. During compilation, the compiler erases all type parameters and replaces each with its first bound (see here). What you get is a request for List<Nissan>
to add(Car)
. But this can't compile because Car
is not a subtype of Nissan
.
In the first case of List<? super Car>
, type erasure would result in List<Car>
and then add(Car)
is valid.
What, I think, will clear your confusion the most is the realization that generics provide compile-time-only checks. As I alluded to above the block of code
static void insertCar(List<? super Car /* or Nissan */> c, Car x) {
c.add(x);
}
has to compile regardless of what arguments the method is called with at runtime. This means that the generics in the 1st method argument have nothing to with the type you pass to the 2nd argument: insertCar(..., Car)
or insertCar(..., Nissan)
do not affect the compilability of c.add(x)
. The given argument is converted (upcasted) to the method's argument type Car
and this is irrelevant to the content of the method.