3
public class SS {
    public static void main(String[] args) {
        SS s = new SS();
        List<B> list = new ArrayList<>();

        s.checkWithoutInheritance(Arrays.asList(new B()));  //Works fine
        s.checkWithoutInheritance(list);    //----------Not working

        s.checkWithInheritance(Arrays.asList(new B())); //Works fine
        s.checkWithInheritance(list);   //Works fine
    }

    private void checkWithInheritance(final Collection<? extends A> s) {
    }

    private void checkWithoutInheritance(final Collection<A> s) {
    }
}


class A {
};

class B extends A {
};
Shubhendu Pramanik
  • 2,711
  • 2
  • 13
  • 23

3 Answers3

3

They don't behave differently, you are using them differently.

Here you are letting the compiler decide the type of the List returned by Arrays.asList, and the compiler decides it should be List<A>, given the argument required by checkWithoutInheritance():

s.checkWithoutInheritance(Arrays.asList(new B()));

Here you are passing a List<B> to the method, so the compiler has no choice but to fail:

List<B> list = new ArrayList<>();
s.checkWithoutInheritance(list);

If you used both Lists the same you'd get the same output:

Both failing:

List<B> list = new ArrayList<>();
List<B> list2 = Arrays.asList(new B());
s.checkWithoutInheritance(list2); 
s.checkWithoutInheritance(list);

Both working:

s.checkWithoutInheritance(Arrays.asList(new B()));
s.checkWithoutInheritance(new ArrayList<>());

As for checkWithInheritance(final Collection<? extends A> s), it accepts both a List<A> and a List<B>, which is why both of your checkWithInheritance calls pass compilation.

Eran
  • 387,369
  • 54
  • 702
  • 768
1

When you have a method:

void checkWithoutInheritance(final Collection<A> s);

The line:

s.checkWithoutInheritance(Arrays.asList(new B()));

works fine, because it's the same as:

List<A> list = Arrays.asList(new B());
s.checkWithoutInheritance(list);

But, the following code:

List<B> list = new ArrayList<B>();
s.checkWithoutInheritance(list); // compiler error

produces a compiler error, because generics are not covariant, and List<B> can not be assigned to the List<A>:

List<B> listb = new ArrayList<B>();
List<A> lista = listb; // Incompatible types error, even though B extends A

When you have a method:

void checkWithInheritance(final Collection<? extends A> s);

<? extends A> wildcard relax the restrictions on the variable s, therefore both are valid:

s.checkWithInheritance(new ArrayList<A>());
s.checkWithInheritance(new ArrayList<B>());

<? extends A> works on lists of A and the subtypes of A.

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
0
List<B> list;

This declares a variable which is a list, with the guarantee that everything in that list is something that can be cast B (i.e. B, a subclass of B, or null), to which you can only add things which can be cast toB`.

private void checkWithoutInheritance(final Collection<A> s) {

This declares a method, accepting a parameter which is a collection, with the guarantee that everything in that collection can be cast to A, to which you can only add things which can be cast to A.

This means that checkWithoutInheritance can do this:

s.add(new A());

If you were able to call

checkWithoutInheritance(list);

then list would now contain an instance of A, which cannot be cast to a B, violating the guarantee on the contents of list.

As such, it is disallowed by the compiler. The compiler doesn't care that you don't add an A to s, because you could. It errs on the side of safety.


On the other hand:

private void checkWithInheritance(final Collection<? extends A> s) {

This declares a method, accepting a parameter which is a collection, with the guarantee that everything in that collection is something which can be cast to some subclass of A.

You can't add an A to it, because you don't know if "some subclass" means A, B, or something else entirely.

As such, you can't write s.add(new A()); in its method body, so it is safe to pass a List<B> to it.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243