Challenge

Fix the signatures with PECS

20 minintermediate28 yrs

Edge cases. You have to reason, and two valid fixes differ.

What this teaches

  • Ask of each parameter: does this method read from it or write to it?
  • ? extends T for a producer, ? super T for a consumer, plain T for both
  • A wildcard on a parameter widens what callers may pass and costs nothing
  • A wildcard on a return type narrows what callers may do, and spreads

Starter

Starter.javaOpen in playground
import java.util.*;

/**
 * CHALLENGE — 20 minutes.
 *
 * Four utility methods, all correct in what they do and all too strict in
 * what they accept. Every call in main() is commented out because none of
 * them compiles.
 *
 * Do not change main(). Change the signatures so the calls compile — with no
 * casts, no raw types, and no @SuppressWarnings.
 *
 * TASKS
 *   1. Uncomment main() and read the four errors.
 *   2. For each method ask the only question that matters: does it READ from
 *      this parameter, WRITE to it, or both?
 *   3. Fix the signatures. One of the four does not want a wildcard at all —
 *      work out which, and why.
 *   4. Annotate each parameter with a comment: producer, consumer, or both.
 */
public class Starter {

    /** Adds up any numbers. Only ever iterates. */
    static double sumOf(List<Number> numbers) {
        double total = 0;
        for (Number n : numbers) {
            total += n.doubleValue();
        }
        return total;
    }

    /** Moves everything from source into sink. */
    static <T> void drainTo(List<T> source, List<T> sink) {
        sink.addAll(source);
        source.clear();
    }

    /** Overwrites the first n slots of dest with src. */
    static <T> void copyInto(List<T> dest, List<T> src) {
        for (int i = 0; i < src.size(); i++) {
            dest.set(i, src.get(i));
        }
    }

    /** Returns the largest element. Reads, and hands one back. */
    static <T extends Comparable<T>> T largest(List<T> items) {
        T best = items.get(0);
        for (T item : items) {
            if (item.compareTo(best) > 0) best = item;
        }
        return best;
    }

    public static void main(String[] args) {
        // Uncomment. All four fail today.

        // System.out.println(sumOf(List.of(1, 2, 3)));
        // System.out.println(sumOf(List.of(1.5, 2.5)));

        // List<Integer> ids = new ArrayList<>(List.of(1, 2, 3));
        // List<Number> everything = new ArrayList<>();
        // drainTo(ids, everything);
        // System.out.println(everything);

        // List<Object> slots = new ArrayList<>(Arrays.asList(null, null, null));
        // copyInto(slots, List.of("a", "b", "c"));
        // System.out.println(slots);

        // System.out.println(largest(List.of(3, 9, 4)));

        System.out.println("nothing to run until the signatures are fixed");
    }
}

Run it locally:

cd exercises/java/generics/generics-variance/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Hints

  1. Hint 1

    Start with sumOf. It only iterates. What does it write? Nothing — so which wildcard does it want?

  2. Hint 2

    drainTo only calls add on its second argument. Same question, other direction.

  3. Hint 3

    copyInto reads one and writes the other. It needs a different wildcard on each parameter — this is Collections.copy's exact signature.

  4. Hint 4

    largest has to compare AND return an element, so the type parameter has to survive. A wildcard alone cannot express that relationship.

Done when

  • Every call in main() compiles with no casts and no raw types
  • No wildcard appears in any return type
  • A comment on each signature names it as producer, consumer, or both

Stretch

largest is declared `<T extends Comparable<T>>`. Make it work for a subtype that is Comparable against its supertype — a PriorityOrder compared as an Order — and explain in a comment why the bound needs a `? super` inside it. Then go and read Collections.max's real signature; it is the same shape.

← Back to Why is List<String> not a List<Object>?