ExerciseChallenge
Challenge
The compliance report that counts everything twice
20 minintermediate2–8 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- javac generates a bridge method so an erased override really overrides
- getDeclaredMethods() returns the bridge alongside the method you wrote
- Current javac copies your annotations onto the bridge — so 'it has no annotation' is not a way to find it
- Method.isBridge() and isSynthetic() are not the same test, and the difference matters
Starter
Starter.javaOpen in playground
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
/**
* CHALLENGE — 20 minutes.
*
* A startup check walks every registered comparator and records which ones
* have been reviewed, so compliance can prove the ordering rules were signed
* off. The report is wrong on every class that implements a generic
* interface, and OrderByAmount is one: it is listed twice, so the totals are
* double-counted.
*
* Nothing is wrong with the annotation, its retention policy, or the class.
* Reflection is reporting something the developer never wrote.
*
* The obvious fix is a trap. Try "only count the ones carrying @Audited"
* before you read further — it changes nothing, and working out why is the
* whole lesson here.
*
* TASKS
* 1. Run it. Two lines for one method. What is the second one?
* 2. Print getParameterTypes()[0] for each to confirm your theory.
* 3. Fix scan() so each developer-written method is reported once and the
* total reads 1.
* 4. In a comment, say which reflective flag you filtered on, and why you
* chose it over the other one.
*/
public class Starter {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Audited {
String reviewer();
}
record Order(String id, int paise) {}
/** A perfectly ordinary comparator. One compare method in the source. */
static final class OrderByAmount implements Comparator<Order> {
@Override
@Audited(reviewer = "anita")
public int compare(Order a, Order b) {
return Integer.compare(a.paise(), b.paise());
}
}
static int scan(Class<?> type) {
int audited = 0;
for (Method m : type.getDeclaredMethods()) {
if (!m.getName().equals("compare")) continue;
Audited note = m.getAnnotation(Audited.class);
System.out.printf(" %s.%s -> %s%n",
type.getSimpleName(),
m.getName(),
note == null ? "NOT REVIEWED" : "reviewed by " + note.reviewer());
if (note != null) audited++;
}
return audited;
}
public static void main(String[] args) {
System.out.println("compliance report");
int audited = scan(OrderByAmount.class);
System.out.println("audited comparators: " + audited + " (expected 1)");
// Sanity check that the comparator itself is fine. It is — this is
// not a bug in OrderByAmount.
List<Order> orders = new ArrayList<>(List.of(
new Order("A-2", 90000), new Order("A-1", 25000)));
orders.sort(new OrderByAmount());
System.out.println("sorted: " + orders);
}
}Run it locally:
cd exercises/java/generics/type-erasure/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
Count the methods named compare. You wrote one. How many does reflection see?
Hint 2
Print getParameterTypes()[0] for each. One of them is Object — you never declared that, so who did?
Hint 3
Try filtering on 'has the annotation'. It will not help, and understanding why not is the point of this tier.
Hint 4
isSynthetic() is true for bridges AND for lambda bodies and inner-class accessors. Decide which test you actually want, and say why in a comment.
Done when
- Each developer-written method is reported exactly once
- The annotation is still found on every method that declares one
- The total says 1 audited comparator, not 2
- A comment states which of isBridge() / isSynthetic() you filtered on and why
Stretch
Add a second comparator written as a lambda rather than a named class, and
run the scanner over the class that holds it. Does your filter still do the
right thing? If the lambda's method vanishes from the report, decide whether
that is correct behaviour or a second bug — and say what a compliance team
would want.