Producer Extends Consumer Super (PECS) principle

This approach is used in order to make your API more flexible if you use generics. Before explaining it, I should remind what generic wildcards are in java.

The wildcards - are some bounders for types when we use generics. They can be two types: upper or lower bounded generics. A lower bounded generics can be used in case if we want to use all classes that are parents of a particular class T.

<? super T>

A upper bounded generics can be applied when we want to work with only classes that inherit from a particular class T.

<? extends T>

Ok, that’s all what we need to know to keep going. Let’s return to PECS. The PECS approach says:

Producer - extends, consumer - super

I need to give more details here. If we have a parameter that produces some object with a generic type, we should use a lower bounded generics. But, if a parameter consumes a some value with a generic type, we should use a upper bounded generic generics for this.

As you can see, it is quite easy to remember. However, it is still not clear how to apply it. Have a look at an example.

/**
 * add numbers from 0 to count to the collection
 * @param collection - collection that numbers will be add to
 * @param count - number of elements
 */
void addElements(Collection<? super Integer> collection, int count) {
    for (int i = 0; i < count; i++) {
        collection.add(i);
    }
}

The method above is a consumer, because it fills a collection with a generic type with some elements. According to the PECS approach, in this case we should use super. Indeed, we don’t care about what a certain type of elements in a collection. We just want to know exactly that elements with type Integer can be put into it safely, in terms of type-safety. A upper bounded generic let us do it.

Another method below is a consumer.

/**
 * find an index of the element
 *
 * @param array   - list of elements
 * @param element - element that we need to find index of 
 * @param <T>     - type of elements
 * @return index of the element if the collection contains it otherwise -1 
 */
<T> int getIndex(List<? extends T> array, T element) {
    int index = -1;
    int currentIndex = 0;
    for (T e : array) {
        if (e.equals(element)) {
            index = currentIndex;
            break;
        }
        currentIndex++;
    }

    return index;
}

In opposite to the previous example, we don’t put elements into a collection. The method just takes elements from a list and compares it to a given parameter. The parameter produces elements.

To sum up, in short words,

if we take elements from a parameter, it is a producer, and if we put elements into a parameter, the parameter is a consumer.