Search code examples
javatypescovariancecontravariance

How to grok bounded wildcards in Java?


In the wikipedia article about co- and contravariance there is an example use case and then an explanatory sentence describing what the type declaration means. I find this extremely useful. After reading the explanation a few times, I feel that I understand what it says.

<T extends Comparable<? super T>> T max(Collection<T> coll);

The bounded wildcard ? super T conveys the information that max calls only contravariant methods from the Comparable interface.

Can somebody explain in a similar language, what the type declaration on the of the andThen() function in the java.util.function.Consumer @FunctionalInterface means:

public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {

e.g.

The bounded wildcard ? super T conveys the information that andThen .... ?


And I have a secondary question: How can I find out myself, what such a type declaration means? E.g. in first example above from the java.util.Collections util class: How are the type bounds of a class - T - able to convey information about what the methods of T are doing? Can anybody point me to the relevant paragraphs in the Java language specification for this?


Solution

  • Possible answer to the first question:

    public interface Consumer<T> {
        void accept(T t);
        default Consumer<T> andThen(Consumer<? super T> after) {
    

    The bounded wildcard ? super T conveys the information that andThen takes Consumers of T or supertypes of T, aka. contravariant Consumers, as arguments.


    Possible answer to the secondary question: https://stackoverflow.com/a/2501513

    Basically - completely independent of generics (!) - method return return types are inherently "covariant" (assumed to be "producers") in the Java language. If overriding a method in a child class, you can always declare a more specific return type.

    Method arguments are of course also "covariant" - you can only pass more specific objects than the method signature specifies. But on subclasses, although the method is technically not overriden for non-parametric arguments - adhering to the Liskov_substitution_principle - it often makes sense to declare "contravariant" argument types in child classes, which - if the name and other arguments are equal - "overloads" the methods in the parent class. "Static" (reference-type-governed) method dispatch will then ensure that the (less specific) child method is called wins. Method arguments are assumed to be "consumed" and then PECS applies. Anyhow, for generic parameters, bridge methods are generated and it is all a bit more hairy. But we will get there.