# NOTES — thirteen files, and six lines that only exist because of erasure

Compile and run from the directory holding the sources:

```
..\..\..\.toolchain\jdk-21\bin\javac.exe -d out *.java
..\..\..\.toolchain\jdk-21\bin\java.exe -cp out Main
```

`javac -Xlint:all` prints nothing and exits 0. `Main` takes about eight seconds, almost all of it in
block 8's warm-up. Seven of the thirteen files are the corpus unchanged: `Sku` and `LineItem` from
`contract/`, `DiscountStage`, `Promotion` and `PercentageCoupon` from `reference/src/`,
`FixedAmountCoupon` and `MultiBuyPromotion` from the two curveball patches. The six that are this
lesson's are `CodeIndex`, `Discounts`, `CartQuantities`, `Boxing`, `Erasure` and `Main`.

Real output, from exactly this code:

```
== 1 - Catalog and CouponBook are one class here, and the label is why ==
  require("TEA")     Sku[code=TEA, name=Loose leaf tea, unitPriceMinor=450]
  require("SAVE10")  PERCENTAGE_COUPON
  catalogue.require("NOPE"): IllegalArgumentException: no such sku: NOPE
  coupons.require("SAVE99"): IllegalArgumentException: no such coupon: SAVE99
  the two messages differ only because "sku" and "coupon" were passed in

== 2 - one class, not two ==
  catalogue.getClass() == couponBook.getClass() : true
  both report                                   : CodeIndex
  List<Sku> and List<String> share a Class too  : true
  Map<String,Sku> and Map<String,Promotion>     : true, both MapN
  Object require(String)
  Object[] toArray(IntFunction)
  T became Object, and the caller got a cast it did not write

== 3 - new T[n] does not exist, so the caller makes the array ==
  toArray(Sku[]::new)                           : 5 skus, first TEA
  toArrayTheWayItLooks()                        : java.lang.ClassCastException
    message : class [Ljava.lang.Object; cannot be cast to class [LSku;
    thrown at: Erasure.whereTheCastActuallyIs(Erasure.java:84)

== 4 - static is shared, because there is one class to hold it ==
  catalogue.lookups()                           : 2
  coupons.lookups()                             : 2
  CodeIndex.lookupsAcrossAllIndexes()           : 4  <- both, added up

== 5 - generics are invariant, arrays are covariant ==
  Sku[] assigned to Object[]                    : compiles, runs
  storing a String in it                        : java.lang.ArrayStoreException: java.lang.String
  the same mistake through a List<Sku> is a compile error, which is the point
  a List<Sku> answers `o instanceof List<?>`    : true
  first element's real class                    : Sku
  so the elements can be checked, the list cannot

== 6 - PECS on Totals.price's second parameter ==
  subtotal                                      : 5899
  total(List<PercentageCoupon>, ...)            : 590
  total(List<Promotion>, ...)                   : 2215
  the mixed list as it was wired up             : [FIXED_AMOUNT_COUPON, PERCENTAGE_COUPON, ITEM_PROMOTION]
  inStageOrder(...) hands back                  : [ITEM_PROMOTION, PERCENTAGE_COUPON, FIXED_AMOUNT_COUPON]
  the caller's list afterwards                  : [FIXED_AMOUNT_COUPON, PERCENTAGE_COUPON, ITEM_PROMOTION]
  the last two lines differ, so the copy is doing real work
  collectInto(List<Object>, ...)                : accepted, 3 promotions written

== 7 - there is no Map<String, int>, so absent is null ==
  quantityOf("TEA")                             : 4
  quantityOf("LAMP"), never added               : 0
  itemCount()                                   : 104
  holdsExactly("MUG", 100)                      : true
  holdsExactlyByReference("MUG", 100)           : true  <- passes, by luck
  holdsExactly("MUG", 200)                      : true
  holdsExactlyByReference("MUG", 200)           : false  <- same code, 200 instead of 100

== 8 - what boxing costs, measured on this JVM ==
  read  5         long[]       1.8 ns   List<Long>       5.3 ns   LongStream      10.2 ns   x2.9
  read  50        long[]       8.5 ns   List<Long>      51.0 ns   LongStream      14.5 ns   x6.0
  read  1000      long[]     235.4 ns   List<Long>     355.9 ns   LongStream     248.3 ns   x1.5
  read  1000000   long[]  246540.0 ns   List<Long> 1122910.0 ns   LongStream  259560.0 ns   x4.6
  build 5         long[]       7.1 ns   List<Long>      24.4 ns                        x3.4
  build 50        long[]      48.4 ns   List<Long>     176.7 ns                        x3.7
  build 1000      long[]    1007.1 ns   List<Long>    3357.7 ns                        x3.3
  heap  1000000   long[]       8.2 bytes/number   List<Long>   28.2 bytes/number   x3.4
        both still reachable here: 1000000 and 1000000
  quantity 100, boxed twice, compared with == : true
  quantity 200, boxed twice, compared with == : false
  line total 450 as Long, compared with ==    : false
  Integer.valueOf caches -128..127, so the first line is true by luck
```

Blocks 1 to 7 reproduce byte for byte. Block 8 will not: the timings move by roughly a fifth between
runs on the same machine, and by more on a different one. What holds is the shape: the boxed column
is 3 to 7 times the array column, and `from-cpp.md` gives the ranges over five runs.

Four lines in there are the lesson. `no such sku: NOPE` next to `no such coupon: SAVE99`, from one
method. `4` where two `2`s were expected. `java.lang.ArrayStoreException` where a compile error would
have been. And `true` then `false` from the same comparison at 100 and at 200.

---

## `CodeIndex.java` — the class that replaces `Catalog` and `CouponBook`

```java
    private final String what;
```

**This field exists only because `T`'s name does not survive compilation.** `Catalog.require`'s
message is `no such sku: NOPE` and `CouponBook.require`'s is `no such coupon: SAVE99`, and the
grader asserts both. A template would take the word from `T`; `typeid(T).name()` has no Java
equivalent. Drop the field and there is no way to write either message from inside `require`. Block
2 shows why: the JVM holds `Object require(String)` and nothing else.

**The alternative is a `Class<T>` token**, which is what a framework passes. `Class<Sku>` would give
you `"Sku"`, so the messages would read `no such Sku: NOPE`. That is a different string from the one
the contract asserts, so the label wins here. Reach for the token when you also need reflection.

```java
    private static long lookupsAcrossAllIndexes;
```

**`static` on a generic class is one field, shared by every parameterisation.** The catalogue and the
coupon book each answered two lookups, and block 4 prints `4` for this field. A C++ template gives
you one counter per instantiation, so the equivalent code there prints `2` and `2`. Nothing in the
Java source hints at the difference.

**This is the consequence with no compile error and no exception**, which is what makes it the
dangerous one. It shows up as a metric that is silently the sum of two things, or as a lazily built
`static` cache that hands `Sku`-shaped entries to a caller asking about coupons. If a generic class
has `static` mutable state, it is shared, and there is no per-parameterisation form of it.

```java
    private long lookups;
```

**Kept next to the static one for exactly that contrast.** Same type, same increment, different
scope, and only the modifier says so. `faded/` makes this line a gap for that reason.

```java
        this.byCode = new LinkedHashMap<>(Objects.requireNonNull(byCode, "byCode"));
```

**A copy, so the index cannot be edited after the cart is built** — `CouponBook`'s own reasoning,
unchanged. `LinkedHashMap` rather than `HashMap` because `all()` hands the values back and the
catalogue's declaration order is the order `Catalog.skus()` promises. J7 is the lesson on that
choice; the generic changes nothing about it.

```java
        if (found == null) {
            throw new IllegalArgumentException("no such " + what + ": " + code);
        }
```

**The null check is the erasure boundary in this method.** `byCode.get(code)` returns `T`, which is
`Object` in the bytecode, and absent is `null`. There is no `optional<T>` and no
`std::map::at` throwing for you. Remove the check and `require` hands back `null`, the caller's
inserted `checkcast` accepts it, and the failure lands wherever the `Sku` is first dereferenced.

```java
    public T[] toArray(IntFunction<T[]> newArray) {
        return byCode.values().toArray(newArray);
    }
```

**The `IntFunction` is the caller supplying what `new T[n]` cannot.** `new T[byCode.size()]` is
`error: generic array creation`, so the only code that can make a `Sku[]` is code that knows the word
`Sku`, and that is the caller writing `Sku[]::new`. The parameter is not ceremony; it is the
component type arriving at run time by the only route left.

**`Collection.toArray(IntFunction)` exists for this**, added in Java 11. The older spelling is
`toArray(new Sku[0])`, which does the same thing and reads worse.

```java
    @SuppressWarnings("unchecked")
    public T[] toArrayTheWayItLooks() {
        return (T[]) byCode.values().toArray();
    }
```

**Kept in the file so it can be run.** Without the suppression `javac -Xlint:unchecked` reports
`warning: [unchecked] unchecked cast / required: T[] / found: Object[]`, and with or without it the
method returns an `Object[]` successfully. Block 3 shows where the failure appears:

```
    thrown at: Erasure.whereTheCastActuallyIs(Erasure.java:84)
```

**Line 84 is the caller's assignment, not the cast.** The cast on this line compiles to nothing at
all; the `checkcast` is at the call site. So the stack trace names a file that did nothing wrong, and
no frame mentions `CodeIndex`. That is the cost of dismissing an unchecked-cast warning, and it is
the reason `toArray` takes the `IntFunction` instead.

---

## `Discounts.java` — the two wildcards, on a real signature

```java
    public static long total(List<? extends Promotion> promotions, List<LineItem> lines, long subtotalMinor) {
```

**`? extends Promotion` because every value moves out of that list and none moves in.** The corpus
declares `List<Promotion>` here, and block 6 prices a `List<PercentageCoupon>` through this one.
Narrow it and that call is
`error: incompatible types: List<PercentageCoupon> cannot be converted to List<Promotion>`, and the
usual repair is a copy into a fresh `ArrayList<Promotion>` at every call site.

**`lines` keeps its exact type on purpose.** `List<LineItem>` is not `List<? extends LineItem>`,
because `LineItem` is a `record` with no subtypes and none possible. A wildcard there would be noise
that a reader has to check for meaning. Wildcards where a hierarchy exists; exact types where one
cannot.

```java
        for (Promotion promotion : inStageOrder(promotions)) {
```

**Reading out of a `? extends Promotion` list as a `Promotion` is always allowed**, and this is the
half of PECS that has no cost. Every element is some subtype of `Promotion`, so `Promotion` is a safe
static type for it. The direction that fails is the other one: `promotions.add(...)` is
`error: incompatible types: PercentageCoupon cannot be converted to CAP#1`.

```java
        List<Promotion> ordered = new ArrayList<>(promotions);
        ordered.sort(Comparator.comparing(Promotion::stage));
```

**The copy is what stops the sort reaching the caller's list, and the wildcard does not do that
job.** `promotions.sort(...)` compiles against a `List<? extends Promotion>`, because `sort` wants a
`Comparator<? super E>` and `Comparator<Promotion>` is one. So `? extends` bought read-only element
access and not an immutable list. Block 6 is the proof: the caller's list comes back in the order it
was wired up.

**Delete the copy and `Totals.price` silently reorders its argument.** A factory that wired
promotions up in a chosen order finds them rearranged, by a method whose name mentions no sort. The
copy costs one allocation per `total()` call, which block 8 puts at tens of nanoseconds.

**`Comparator.comparing(Promotion::stage)` works because `DiscountStage` is an enum**, so its
`compareTo` is declaration order, and `DiscountStage` declares the requirement's order. `List.sort`
is stable, so promotions within a stage keep the order they arrived in.

```java
    public static void collectInto(List<? super Promotion> sink,
                                   List<? extends Promotion> standing,
                                   CodeIndex<? extends Promotion> couponBook) {
```

**`? super Promotion` on `sink` because every value moves into it.** Block 6 passes a `List<Object>`
and it is accepted: anything that can hold an `Object` can hold a `Promotion`. Declare it
`List<Promotion>` and a caller keeping a wider list of everything it has assembled cannot call this.

**Both wildcards in one signature is the normal case, not a clever one.** `sink` is written, so
`super`; `standing` and `couponBook` are read, so `extends`. `Collection.addAll(Collection<? extends E>)`
in the JDK is the same split for the same reason.

---

## `CartQuantities.java` — what `Map<String, int>` would have prevented

```java
    private final Map<String, Integer> byCode = new LinkedHashMap<>();
```

**`Integer`, because a type argument cannot be a primitive.** Erasure writes `Object` and an `int` is
not one, so `Map<String, int>` does not exist and never will. The substitution brings two properties
an `int` never had: the value can be `null`, and two of them can be separate objects holding one
number. Both of the gaps below are one of those two.

```java
    public int quantityOf(String skuCode) {
        return byCode.getOrDefault(skuCode, 0);
    }
```

**`getOrDefault` is the whole of "a sku the cart does not hold has quantity zero".** Write
`return byCode.get(skuCode)` and it still compiles, because `javac` inserts the unboxing to match the
`int` return type. The first sku nobody has added gives:

```
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return
value of "java.util.Map.get(Object)" is null
```

**The unboxing is invisible in the source**, so the message names `intValue()`, a method that does
not appear on the line. Helpful NPE messages, on by default since Java 15, are why that sentence
names the map call at all.

```java
    public boolean holdsExactly(String skuCode, int quantity) {
        return quantityOf(skuCode) == quantity;
    }
```

**Both sides are `int` here, so `==` compares numbers.** That is what makes this line answer the same
way for 4 socks and for 400.

```java
    public boolean holdsExactlyByReference(String skuCode, int quantity) {
        Integer held = byCode.get(skuCode);
        return held == Integer.valueOf(quantity);
    }
```

**Kept so it can be run, because reading it is not enough to distrust it.** Block 7:

```
  holdsExactlyByReference("MUG", 100)           : true  <- passes, by luck
  holdsExactlyByReference("MUG", 200)           : false  <- same code, 200 instead of 100
```

**`Integer.valueOf` caches −128 to 127**, so the two boxes are one object below 128 and two objects
above it. `ShoppingCartApi` accepts any positive quantity, so both branches are reachable from a
legal call. J6 is the lesson on `==` against `.equals`; the new fact here is that no `new` appears in
the source, because the compiler allocated the object.

---

## `Erasure.java` — the demonstrations that need a running JVM

```java
        System.out.println("  catalogue.getClass() == couponBook.getClass() : "
                + (catalogue.getClass() == couponBook.getClass()));
```

**`true`, and this is erasure observed rather than described.** Two parameterisations, one `Class`
object, because there is one class file. `List<Sku>` and `List<String>` answer the same way. Nothing
can be built on those differing: not a per-type registry keyed on `Class`, and not a `switch` over
parameterisations.

```java
        Arrays.stream(CodeIndex.class.getDeclaredMethods())
```

**Reflection reports the erased signatures**, which is why block 2 prints `Object require(String)`.
The unerased form is in a `Signature` attribute and reachable through
`getGenericReturnType()`, and it is metadata for compilers rather than something dispatch uses.
`javap -s` shows both side by side.

```java
        Object[] anything = catalogue;             // legal: arrays are covariant
        anything[0] = "TEA";                       // a String into a Sku[]
```

**Two lines that compile and should not.** `Sku[]` is an `Object[]`, so the assignment is legal, and
the store is checked at run time instead: `java.lang.ArrayStoreException: java.lang.String`. Every
array store in Java carries that check, which is a cost paid on all of them for a rule that predates
generics.

**The same mistake through a `List<Sku>` does not compile at all.** That is the asymmetry: generics
chose the compile error, arrays could not, and the two features disagree inside one language.

```java
        System.out.println("  a List<Sku> answers `o instanceof List<?>`    : " + (o instanceof List<?>));
```

**`List<?>` is the only list shape `instanceof` accepts**, and it tells you nothing about the
elements. `o instanceof List<Promotion>` is
`error: Object cannot be safely cast to List<Promotion>`. So a `List<Sku>` answers `true` to "is this
a list", and the only way further is to pull an element out and check that.
