3

It's not a duplicate question because I specifically ask how does the compiler allow to add a Cat to a List<? super Cat> catList collection which ALREADY contains a Dog.You see animaList already contains Cats and Dogs because they're Animals but calling addCat which takes a List<? super Cat> allows adding a new Cat i.e. add(new RedCat()); despite that the list already contains a Dog. Shouldn't the compiler disallow this in order to fulfil the List<? super Cat>signature in that it tries to add Cats to a list already containing Dogs?

However explicitly calling

catList.add(new Dog());

enforces the constraint.So the question is why the difference in behaviour?

Original question: I've been playing with Java generics and noticed a strange(?) issue. In the following code snippet you can add both a Dog and Cat into the animalList and then pass this list into addCat(List<? super Cat> catList)
and add another Cat instance.So we have a List with both Dogs and Cats which overides the ? super Cat boundary, in that it should not allow Dogs?

However as soon as you uncomment the

//catList.add(new Dog());

line, you get a compile time error

(argument mismatch; Dog cannot be converted to CAP#1)

What's going on here? The code is amended from this tutorial

 package com.tutorialspoint;

    import java.util.ArrayList;
    import java.util.List;

    public class GenericsTester {

       public static void addCat(List<? super Cat> catList) {
         catList.add(new RedCat());
         catList.add(new Cat());

         //catList.add(new Dog());

          System.out.println("Cat Added");
       }

       public static void main(String[] args) {
          List<Animal> animalList= new ArrayList<Animal>();
          List<Cat> catList= new ArrayList<Cat>();
          List<RedCat> redCatList= new ArrayList<RedCat>();
          List<Dog> dogList= new ArrayList<Dog>();

          animalList.add(new Dog());
          animalList.add(new Cat());
          animalList.add(new RedCat());

          addCat(animalList);

            System.out.println("all ok");
       }
    }
    class Animal {}

    class Cat extends Animal {}

    class RedCat extends Cat {}

    class Dog extends Animal {}
microwth
  • 1,016
  • 1
  • 14
  • 27
  • The compiler doesn’t know enough about your `List super Cat>` to allow you to add a dog to it. Some such lists should allow dogs, other should forbid them, so what can the compiler do? The compiler plays it safe and forbids it to be sure nothing wrong is done. – Ole V.V. Dec 14 '17 at 23:44
  • 2
    You are distracted by the list. That the list already contains a dog is irrelevant. The function `addCat` restricts itself to only allow adding cats (and super classes). – Micha Wiedenmann Dec 15 '17 at 10:31

2 Answers2

5

A List<? super Cat> could be a List<Cat>, a List<Animal>, or a List<Object>. Your method should work with any of these possibilities.

Because you cannot add a Dog to a List<Cat>, you therefore cannot add it to a List<? super Cat>.

Joe C
  • 15,324
  • 8
  • 38
  • 50
1

You should read about PECS (Producer Extends Consumer Super) and why that rule is the way it is. It'll also explain why Java collections are invariant in nature, and what kind of assignments are allowed.

Given that your collection takes any super class of Cat and it is a collection definition which can consume, it can only take Cat, Animal or Object

Dev Amitabh
  • 185
  • 1
  • 4