Fatih Arslan
Fatih Arslan

Reputation: 1114

PECS For generics in Non Collections

Joshua Bloch came up with the PECS, which says the rule when to use ? extends T and ? super T. If you think about PECS in terms of Collections framework, then it is very straightforward. If you add values to the data structure, use ? super T. If you read from the data structure, use ? extends T. for instance:

public class Collections {  
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        for (int i = 0; i < src.size(); i++)   
            dest.set(i, src.get(i));   
    }   
}

If I check the signature of

public static <T> void sort(List<T> list, Comparator<? super T> c) 

I see Comparator uses ? super, so it should be a consumer. Looking at the code, comparator c is only used to produce stuff because it is asked the logic of comparing.

On one hand, I understand why it is a super because as a developer I want to use comparators of class T and also comparators of super class T because objects of T is also of type super classes of T. But when I try to think in terms of PECS, I fail to understand.

Does PECS only hold for Collections framework? If not, Can someone explain to me what does comparator consume in Collections.sort?

Upvotes: 2

Views: 403

Answers (1)

fps
fps

Reputation: 34470

For the sake of this answer, let's take Comparator as the main, guiding example.

If you think carefully about it, you'll see that Comparator actually receives two arguments of type T and returns the result of their comparison (represented by an int). In other words, it consumes two instances of type T and produces an int value. So, as by the PECS rule, it is a consumer of T, hence the use of ? super T.

More generally, you should consider producer and consumer from the point of view of the main type with regard to the types of each one of its generic parameters. If some Comparator type consumes objects of type T, the PECS rule states that users of such Comparator<T> could use it to compare objects whose type is a subtype of T.

As a specific example, if you happen to already have the logic to compare two generic Number instances (no matter what their concrete type actually is), you could use it i.e. to compare Double instances, because doubles are numbers, after all.

Consider the following comparator:

Comparator<Number> c = Comparator.comparingInt(Number::intValue);

Here, the comparator c compares Number instances (any number) by taking into consideration only their integral part.

If you have the following list of Double instances:

List<Double> doubles = Arrays.asList(2.2, 2.1, 7.3, 0.2, 8.4, 9.5, 3.8);

And the following sort method:

static <T> void sort(List<T> list, Comparator<T> c) {
    list.sort(c);
}

(Note the absence of the wildcard ? super T in the Comparator argument).

Then, if you want to sort the List<Double> doubles list, the signature of the above sort method would require you to pass a concrete Comparator<Double>. But what if you wanted to use your previously defined c comparator to sort the List<Double> doubles?

As that comparator's type is Comparator<Number>, and as the doubles list's type is List<Double>, the following code would produce a compilation error:

sort(doubles, c);

Fortunately, as Comparator is a consumer of the type of the elements it compares, you can change the signature of the sort method to:

static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

And now, this code would compile:

sort(doubles, c);

Upvotes: 4

Related Questions