CS2030 AY19/20 Semester 2
  • Introduction
  • Textbook Contributions
  • Peer Learning Tasks
  • Piazza Participation
  • Setting Up Lab Environment
  • Setting Up Java
  • Setting Up Vim
  • Setting Up MacVim
  • Setting Up Sunfire and PE Nodes
  • Setting Up Checkstyle
  • Textbook
  • CS2030 Java Style Guide
  • CS2030 Javadoc Specification
  • JDK 11 Download Link
  • Codecrunch
  • Github Repo
  • Piazza Forum

  • Generics Manifesto Part One


    Edit the material here!

    The point of this "manifesto" series is to go a bit more in-depth into fundamental generic concepts, and to share some subtle insights and possibly confusing gotchas.

    1. PECS

    The PECS principle, which stands for Producer-Extends-Consumer-Super, is a guideline to follow when using bounded wildcards.

    Consider the classes Vehicle, Car, Bus, and Roomba, where Car and Bus extends Vehicle and Roomba extends Car.

    List<? extends Vehicle> garage = new ArrayList<>();
    garage.add(new Vehicle()); // compilation error
    garage.add(new Car()); // compilation error
    garage.add(new Bus()); // compilation error
    garage.add(new Roomba()); // compilation error

    The errors above seem at first to be uncalled for - garage is a List of "any objects that extend Vehicle" (including Vehicle itself), so it should be able to accept our Car, Bus, and Vehicle objects.

    What we're missing here is that List<? extends Vehicle> doesn't actually mean a list that can contain any child objects of Vehicle. List<? extends Vehicle> is in fact saying that it is a "list parameterized to one concrete class that extends Vehicle".

    So List<? extends Vehicle> is one of List<Car>, List<Truck>, or List<Roombar>, but the compiler does not know which. And because the compiler does not know which, it does not allow the addition of, say, a Vehicle to List<? extends Vehicle> - what if the unknown identity of List<? extends Vehicle> is List<Car>? It wouldn't make sense to add a Vehicle to a list of Cars. The same goes for adding a Car or Bus or Roomba to List<? extends Vehicle>.

    What if instead of trying to add to garage, we only want to get from it?

    List<? extends Vehicle> garage = new ArrayList<>(List.of(new Car(), new Bus()));

    Vehicle vehicleOne = garage.get(0);
    Vehicle vehicleTwo = garage.get(1);

    We find ourselves knowing intuitively what type to declare vehicleOne and vehicleTwo as. Since we are absolutely sure that garage is a list of a subtype of Vehicle, we know that assigning its children to Vehicle will work. It is in this sense that people say garage is a "producer" of sorts - it produces values, and we consume from it, but we can never insert values into it.

    Applying this pattern into the context of a method would look something like this :

    List<Vehicle> factory = new ArrayList<>();
    public void addAllToFactory(List<? extends Vehicle> vehicles){
    for(Vehicle oneVehicle : vehicles){
    factory.add(oneVehicle);
    }
    }

    List<Car> cars = new ArrayList<>();
    cars.add(new Car());
    addAllToFactory(cars); //works

    The cars that we pass into addAllToFactory now takes on the role of a "producer", yielding the values inside it for the factory to "consume". And since it a "producer", the vehicles parameter inside addAllToFactory uses the corresponding wildcard-extends (PE in PECS).

    A similar logic applies for Consumer-Super.

    List<? super Car> garage = new ArrayList<>();
    garage.add(new Car()); //runs
    garage.add(new Roomba()); //runs
    garage.add(new Vehicle()); // compilation error

    The compiler knows that garage references a supertype of Car, but doesn't know which one. I can add Roomba and Car to garage because they're all subtypes of Car, but Vehicle needs to stay out. The type of garage may or may not be a parent of Vehicle - so in order to avoid the possibility of a runtime error if garage turns out to be a subtype of Vehicle, the compiler plays it safe and throws a compilation error.

    Adding stuff to garage makes it take on the role of a consumer; what if we want it to produce?

    Car car = garage.get(0);         //error
    Car car = (Car)garage.get(0); //runs
    Object object = garage.get(0); //runs

    Because the compiler doesn't know the exact type of garage, it only accepts the assignment of a returned value to an Object, since all classes are Objects. If I want to assign it to something else, like Car, then I need to be sure that the first element of garage if of type Car so I can downcast it. All these restrictions makes wildcard-super a pretty poor producer.

    An example of wildcard-super doing what it does best - acting as a consumer:

    List<Car> cars = new ArrayList<>(List.of(new Car(), new Car()));
    public void addCarsToFactory(List<? super Vehicle> factory){
    for(Car car : cars){
    factory.add(car);
    }
    }

    List<Vehicle> factory = new ArrayList<>();
    addCarsToFactory(factory); //works

    The factory parameter in addCarsToFactory is a consumer, so it uses super (CS of PECS).