I don’t understand Java wildcards very well. I don’t understand why this doesn’t compile
Person marie = new Person("Marie", "Brown");
List<? extends Person> extendsPersonList = new ArrayList<Person>();
extendsPersonList.add(marie);
but this does
Person marie = new Person("Marie", "Brown");
List<? super Person> superPersonList = new ArrayList<Person>();
superPersonList.add(marie);
In both cases ?
stands for an unknown type that must include Person
(since it’s a subtype and a supertype of itself). But the lists behave completely differently. Why?
ChatGPT said the compiler can’t be sure it’s a list of Person
and not a list of some Person
subtype so it can’t approve any addition to the extends
list. But even if that was the case (suppose, prior to the addition, it only contained Student
s), Person
would still conform to the bound, wouldn’t it? Then why can’t it add that object?
UPD: I came up with a way to explain it. Is it an accurate explanation?
First, let me tell you about casting in Java. There are two kinds of casting: narrowing casting (casting down from a parent to a child) and widening casting (casting up from a child to a parent). Casting down is what the Java compiler never does. In other words, it can’t be performed implicitly. Why? Because the compiler can’t be sure it’s safe. Imagine
Student
has an extra fieldUniversity
that is absent in its parent, thePerson
class. Even if such fields (unique to a child) are initialized with default values, in this casenull
asUniversity
would be a reference type, accessing those fields later may result in exceptions, in this caseNullPointerException
. So whenever you do downcasting you do it explicitly, taking full responsibility for any potential repercussions. You basically tell the compiler, “I know more about this object than you do, I’m sure it’s safe to do, and I’ll do it”. Explicit casting requires a cast operator, which is a name of a class in parentheses, for examplePerson person = (Person) object
. It often happens inequals()
implementations immediately after you test an object type withinstanceof
.Now, let’s get back to wildcards. One thing you should understand is that
List<? extends Person>
doesn’t mean “aList
of anything that isPerson
” (List<Person>
means that). Instead, it means “aList
of some specific but unknown type that extendsPerson
”. Some options are: aList
ofPerson
, aList
ofStudent
, aList
ofEmployee
(assumingStudent
andEmployee
arePerson
subclasses). Whenever you add an element to aList
, the compiler has to perform an implicit cast. As you already know, an implicit cast can only be up. So whenever you addPerson
toList<? super Person>
, the compiler is happy: it knows that even if the actual type of theList
is somePerson
supertype, for exampleObject
, it’s guaranteed that it will have no problem upcastingPerson
toObject
. But whenever you try to add aPerson
toList<? extends Person>
, the compiler is not happy. If the actual type of theList
turns out to beList<Student>
, for example, there would arise a need for an unsafe downcasting which is off the table with the Java compiler. As a result, it refuses to greenlight the addition
UPD2: For some reason, this post was flagged as needing “moderator attention”. Please let me know which part of it is problematic