-2

i was trying to compile this and could not figure out few things. I will appriciate your help.

  1. What is the meaning ofList<? extends Exception> lst = new ArrayList<Exception>(); why this line can be compield? Because for example this isnt compile

List<NullPointerException> lst2 = new ArrayList<Exception>();

  1. Lets assume that we have the code as follows:

public class examsQ<S> {

    
    public void f3(Collection<? extends S> c, List<S> l ) {
        c = l;
    } 
}

I cant understand why this code compiles. For example, is c is a collection of subclass of S how can it point to l? Thank you!

  • 2
    Hi, and welcome to StackOverflow. One question : those bits of code above, did you (1) copy / paste directly from your IDE, or (2) type them into the question. I’m guessing you typed then, because neither would compile. please ALWAYS copy/paste code, never type them. – racraman Jun 26 '22 at 00:55
  • hi ! sorry about that! fixed it!:) – JavaCodersince2022 Jun 26 '22 at 01:16
  • `List extends Exception> lst` means that `lst` will hold list of *some specific* subtype of Exception (including Exception itself). This means we can assign to it `new ArrayList` but also `new ArrayList`, `new ArrayList`, etc. We don't know which *specific* type of Exception `lst` will handle, so we can't *add* new elements *via* `lst` (so `lst.add(new NullPointerException())` is not legal since `lst` may at that time hold `List`. BUT we can *read* from it any value and treat it as Exception. – Pshemo Jun 26 '22 at 01:49

1 Answers1

4

Java has call-site variance, which means the caller gets to decide which direction the subclass relationship goes in generic relationships.

Let's assume we've got a class Parent and a subclass called Child. Generally speaking, it's not safe to treat a List<Child> as a List<Parent> or vice versa. If we try to cast a List<Child> to a List<Parent> then someone could come along and .add something that isn't a Child to it, breaking our list. Conversely, if we try to cast a List<Parent> to a List<Child> and it contains some things that aren't Child instances, we're equally out of luck.

However, each cast still provides us with a guarantee. If I have a List<Child>, then I know that every element of that list is a Child and hence (by the 'is-a' relationship established by subclasses) every element of that list is a Parent. So if we only intend to read from the list and never to write to it, it's safe to take a List<Child> and treat it as a read-only version of List<Parent>. That's exactly what List<? extends Parent> is. It says "I've got a list, it contains some subclass of Parent and I don't know which one. It's safe to get elements from this list and cast them up to Parent since, no matter what class the ? represents, it's always safe to assume it's a Parent subclass. So an argument of type List<? extends Parent> can accept a List<Parent> or a List<Child> or a list of any subtype of Parent. But we can't add elements to a List<? extends Parent> because we don't know what type the ? is, so we can never safely supply an argument to List.add. This is called a covariant relationship, where it's safe to consider a list of a subclass to be a list of the superclass for these purposes.

The converse relationship is called contravariant. It's the exact same idea, but it's a little harder to understand sometimes. In Java, we would write it as List<? super Child>. A List<? super Child> is a list of some supertype of Child. That ? could be Parent or it could be Object or it could be some interface that Child happens to implement, but it has to be a supertype of Child. Now, we can never safely get elements from this list, since we have no idea what the type is. It's not Child, since the list could be a List<Parent> and could contain non-Child instances. However, we can add elements to the list. Whatever the ? is, it's a supertype of Child, so we can always safely add Child instances to List<? super Child>, since the argument to add can just be quietly upcast to the correct type.

Going back to your examples,

List<? extends Exception> lst = new ArrayList<Exception>();

The left-hand type is a list of some subtype of Exception. Notably, ArrayList<Exception> implements List<Exception> and, since Exception is (trivially) a subtype of itself, List<Exception> is a valid List<? extends Exception>.

List<NullPointerException> lst2 = new ArrayList<Exception>();

Here, ArrayList<Exception> is still a subtype of List<Exception>, but List<Exception> is incompatible with List<NullPointerException>, because in the absence of any variance annotations (extends or super), all generic arguments are assumed to be invariant (except arrays, but that's a whole other can of unsound worms). We could have written

List<? super NullPointerException> lst2 = new ArrayList<Exception>();

and gotten a write-only list of NullPointerException, or we could have written

List<? extends Exception> lst2 = new ArrayList<NullPointerException>();

and gotten a read-only list of Exception. But we have to specify which we want. Likewise,

public void f3(Collection<? extends S> c, List<S> l) {
    c = l;
}

List<S> implements Collection<S>, and S is (again, trivially) a subtype of S, so we have Collection<S> compatible with Collection<? extends S>, hence the assignment compiles.

More useful reading material on when you want extends vs. super: What is PECS (Producer Extends Consumer Super)?

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116