ExerciseChallenge
Challenge
Fix the signatures with PECS
20 minintermediate2–8 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 StarterHints
Hint 1
Start with sumOf. It only iterates. What does it write? Nothing — so which wildcard does it want?
Hint 2
drainTo only calls add on its second argument. Same question, other direction.
Hint 3
copyInto reads one and writes the other. It needs a different wildcard on each parameter — this is Collections.copy's exact signature.
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.