---
title: 'Object-Oriented Java'
source: 'https://academia.sh/en/courses/java-object-model'
language: en
updated: '2026-08-17T04:41:20+00:00'
license: 'CC BY-SA 4.0'
lessons: 13
outcomes:
    - 'Builds designs by modeling class, interface, and inheritance relations correctly'
    - 'Uses generics for type safety'
    - 'Manages the exception hierarchy with the checked/unchecked distinction'
---

# Object-Oriented Java

In Java a name carries two types, and the course measures every language decision with a single question: did the declared type give the result, or the runtime type? From classes, inheritance, interfaces, inner classes, and records to the type erasure of generics and the exception hierarchy, every topic is built on which side of that distinction it stands.

## Classes and Interfaces

Object lifecycle and constructors, encapsulation through visibility levels, where overriding and hiding come apart, the choice between an abstract class and an interface, class-level state and initialization order, the extension restriction, nested class forms, and data-carrier types.

### Class, Object, and Constructor

Source: https://academia.sh/en/courses/java-object-model/class-object-and-constructor

The constructor chain runs top to bottom, and an overridden method called from the chain's upper link comes from the runtime type: since the subclass's fields are not yet assigned, the result comes out `sub: null x0`; in a fragile version the object is never delivered — the trap does not arise in a private or final method.

The Java Fundamentals course used a class only as a **container**: what was measured was what
the source left in the class file, the class itself never entered the measurement. Even at the
closing course, reading assignment's single model, a class was still a frame — a field, a
method, nothing more.

In this course, the container becomes the subject. The moment it does, something new appears
in the source: **a name starts carrying two types.** When a variable is declared, the compiler
sees one written type; the actual class of the object assigned to it can be something else
entirely. Every lesson here asks a single question: **which one made this decision?** The
first lesson asks it at the moment an object is constructed, because during construction the
two types do not agree with each other for a while.

#### One Name, Two Types

A declaration carries two separate pieces of information. In `Base d = new Sub();`, `Base` is
the **declared type**: the type the compiler looks at while resolving the name, deciding which
member can be written. `Sub` is the **runtime type**: the object's real class, unchanged until
garbage collected. In most programs the two are written the same, and the difference does not
show.

The course deliberately separates them. The core is a base class (`Base`) and a subclass
(`Sub`) extending it. `Sub` **overrides** one of `Base`'s methods — puts its own version under
the same signature — and also declares a field with the same name. A single reference is set
up: declared type `Base`, runtime type `Sub`. The oracle is the rig itself; since we separated
the two types ourselves, which one an answer comes from is known from the start.

This lesson's question is: while the object is still **under construction**, what state are
the two types in? A constructor is not a method, but its body can contain a method call. When
that call runs, which object is there?

Object-oriented programming's concepts — object, class, inheritance, interface — were
established in the Programming Fundamentals course and are not repeated here. What is measured
is not the concept, it is **which side the decision comes from**.

#### The Measurement Core

The measurement is a **setup trace**: every initialization step writes its name and the result
it sees at that moment to a shared list, and the list is printed once at the end.

- **CI1** — Declared type `Base`, runtime type `Sub`, is set up by the rig itself; which type
  every line comes from is read from the rig, not from inference.
- **CI2** — The trace is written to a shared list and printed at the program's end; printing
  does not mix into the measured order.
- **CI3** — The `Sub` class's `count` field has no initial value and is assigned only in the
  constructor body; since its unassigned state shows as **0**, field initialization and the
  constructor body can be told apart.
- **CI4** — The measurement reads no environment-dependent data: duration, memory address,
  identity hash, and path are not written. The only things printed are order and value.

```java
// Base.java — the course's core pair and the setup trace
import java.util.*;

class Base {
    static final List<String> LOG = new ArrayList<>();
    static void log(String s) { LOG.add(s); }
    static void dump() { LOG.forEach(System.out::println); LOG.clear(); }

    String label = "base field";

    Base() {
        log("2. Base constructor    | name() -> " + name());
        log("                       | describe() -> " + describe());
    }

    String name() { return "base method"; }
    String describe() { return "base: " + label; }
}

class Sub extends Base {
    String label = "sub field";
    int count;
    { log("3. Sub field init      | describe() -> " + describe()); }

    Sub(int count) {
        log("4. Sub ctor body       | describe() -> " + describe());
        this.count = count;
        log("5. after assignment    | describe() -> " + describe());
    }

    @Override String name() { return "sub method"; }
    @Override String describe() { return "sub: " + label + " x" + count; }
}

class Fragile extends Base {
    String label = "fragile field";
    @Override String describe() { return "fragile: " + label.length(); }
}
```

`Sub`'s constructor has **no** `super()` call written in it. When it is not written, the
compiler places one at the chain's start itself; a constructor's first job is always to run
the base class's constructor. The chain does not end at `Base` either — it runs up to the root
class.

#### In What Order Does the Constructor Chain Run

```java
// Chain.java — which type decides while an object is under construction
public class Chain {
    public static void main(String[] args) {
        Base.log("1. new Sub(3) called");
        Base d = new Sub(3);
        Base.log("6. setup done          | describe() -> " + d.describe());
        System.out.println("declared type Base, runtime type "
                + d.getClass().getSimpleName());
        System.out.println();
        Base.dump();

        System.out.println();
        Fragile k = null;
        try {
            k = new Fragile();
        } catch (NullPointerException e) {
            System.out.println("fragile setup   : " + e.getClass().getSimpleName());
        }
        System.out.println("delivered object: " + k);
    }
}
```

```
declared type Base, runtime type Sub

1. new Sub(3) called
2. Base constructor    | name() -> sub method
                       | describe() -> sub: null x0
3. Sub field init      | describe() -> sub: sub field x0
4. Sub ctor body       | describe() -> sub: sub field x0
5. after assignment    | describe() -> sub: sub field x3
6. setup done          | describe() -> sub: sub field x3

fragile setup   : NullPointerException
delivered object: null
```

The order is four steps and runs in one direction: **base constructor, sub field
initializers, sub constructor body, delivery.** Even though `new Sub(3)` is what got written,
the first body to run is `Base`'s constructor. This is inheritance's counterpart on the
constructor side — an object comes into being only once every layer above its own class's
layer has already been set up.

The second line is this lesson's measurement. `Base`'s constructor writes `name()` and gets
**`sub method`** back. The one writing the call is `Base`, the one running is `Sub`'s version:
an overridden method's decision is made by the **runtime type**, and this rule is not
suspended even in the middle of the constructor chain. Even while the object is only half
built, its real class is already settled.

The third line is what that costs. The `describe()` call at that same point gives **`sub: null
x0`**. `Sub.describe()` reads its own `label` field, but that field has not been assigned yet:
field initializers run **after the base constructor finishes**. So the runtime type has chosen
the correct method, and the chosen method is looking at a state that does not exist yet. The
two decisions come from separate sides, and a gap remains between them.

The gap's width is read between the third and fifth lines. On the third line, `label` is
assigned, `count` is still **0**; by the fifth, both are in place. Because `count` is assigned
in the constructor body (**CI3**), the two stages can be told apart: values given at
declaration sit in one place, those given in the constructor body settle later. Someone
reading the source sees `label` and `count` as two fields of the same class; setup order
scatters them across three separate moments.

#### The Cost of a Half-Built Object

The output's last two lines are the gap's real cost. `Fragile` does the same job as `Sub`,
with one difference — the method it overrides not only reads the field but calls on it too.
The result is a **`NullPointerException`**, and where it is born matters: the exception falls
inside `Fragile.describe()`, but the line calling that method sits in `Base`'s constructor.
The class writing the defect and the class where it shows are not the same.

The last line measures the consequence: the `k` reference is **`null`**. Since the constructor
never completed, `new` produced no value at all. A half-built object is not delivered, but
before it was delivered, a method already ran on it — the subclass's method at that.

The rule that follows is this course's first concrete contract: **a constructor should not
call a method that can be overridden.** When it does, the code it writes runs the subclass's
version, and whoever wrote the subclass wrote it assuming its fields were already assigned.
Each side is internally consistent; the defect is born exactly where the two meet, and it is
invisible looking at either the base class's source or the subclass's alone.

#### A Bounding Measurement: No Trap in a Private or Final Method

If the rule held everywhere, it would not explain anything. There are two situations where the
trap does not arise, and both come from the same reason: for those methods, there is no
**overriding** in play at all.

- **CI5** — The three methods in the boundary measurement carry the same body shape and differ
  only in their modifiers: one private, one `final`, one unmarked. In the subclass, all three
  have a counterpart written under the same name, except the `final` one — that one cannot be
  written.

```java
// Boundary.java — no trap arises in a private or final method
public class Boundary {
    public static void main(String[] args) {
        BoundaryBase s = new BoundarySub();
        System.out.println("class of constructed object : " + s.getClass().getSimpleName());
    }
}

class BoundaryBase {
    BoundaryBase() {
        System.out.println("from constructor restricted(): " + restricted());
        System.out.println("from constructor fixed()      : " + fixed());
        System.out.println("from constructor open()       : " + open());
    }

    private String restricted() { return "base restricted"; }
    final String fixed()  { return "base fixed"; }
    String open()         { return "base open"; }
}

class BoundarySub extends BoundaryBase {
    String label = "sub field";

    private String restricted() { return "sub restricted: " + label; }
    @Override String open() { return "sub open: " + label; }
}
```

```
from constructor restricted(): base restricted
from constructor fixed()      : base fixed
from constructor open()       : sub open: null
class of constructed object : BoundarySub
```

Three lines, three separate sides. The constructed object's class is the same in all three —
the fourth line says so — but only the last one takes its answer from the runtime type.

The `restricted()` call gives **`base restricted`**. A private method is never inherited by a
subclass at all; the same-named method in the subclass is not its override, it is a **separate
method**. With no overridden version at all, the call's only destination is `BoundaryBase`'s
own method — the runtime type has nothing to add.

The `fixed()` call gives **`base fixed`**. A `final` method cannot be overridden; a version
under the same signature written inside `BoundarySub` would have been rejected by the
compiler. With no other version to be found in the subclass, the only place the call can go
is the base class. The restriction itself is measured separately later in this course; what
shows here is its consequence during setup.

The `open()` call, in turn, gives **`sub open: null`**. An unmarked method can be overridden,
has been overridden, and the runtime type decides — followed by the familiar `null`.

The boundary's meaning is this: whether a call made from a constructor is dangerous cannot be
read from how the call is written. All three lines are written the same way. What makes the
difference is the **method's modifier** — that is, how much the base class leaves open to the
subclass. Declaring a helper method meant to be called from a constructor as private or
`final` turns the trap into not a warning, but a rule of the language.

#### Where Does the Call Bind in the Class File

The three calls were seen to be indistinguishable in the source. Are they distinguishable in
the class file? For the measurement, `BoundaryBase` is compiled and the call instructions in
its constructor's body are read, along with the modifiers of those calls' targets.

- **CI6** — The measurement compiles only the `BoundaryBase` class; the subclass is not even
  present. What is read is not a run, it is the file the compiler produces for a single class.

```java
// Gauge.java — reads the class file: the calls in a constructor and their targets' modifiers
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    record Call(String name, String instruction, String target, String modifier, boolean overridable) {}

    static List<Call> constructorCalls(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Path f = d.resolve(className + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), f.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        ClassModel cm = ClassFile.of().parse(d.resolve(className + ".class"));
        Map<String, String> modifiers = new HashMap<>();
        for (MethodModel m : cm.methods()) {
            var flags = m.flags();
            modifiers.put(m.methodName().stringValue(),
                    flags.has(AccessFlag.PRIVATE) ? "private"
                            : flags.has(AccessFlag.FINAL) ? "final" : "-");
        }
        List<Call> calls = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals("<init>") || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get())
                if (e instanceof InvokeInstruction iv) {
                    String name = iv.name().stringValue();
                    String target = shortName(iv.owner().asInternalName());
                    String mod = target.equals(className) ? modifiers.getOrDefault(name, "-") : "-";
                    calls.add(new Call(name, iv.opcode().name().toLowerCase(Locale.ROOT),
                            target, mod, target.equals(className) && mod.equals("-")));
                }
        }
        return calls;
    }

    static String shortName(String internal) { return internal.substring(internal.lastIndexOf('/') + 1); }
}
```

```java
// Dispatch.java — which instruction do the calls in a constructor bind to in the class file
public class Dispatch {
    static final String SOURCE = """
        class BoundaryBase {
            BoundaryBase() { restricted(); fixed(); open(); }
            private String restricted() { return "base restricted"; }
            final String fixed()  { return "base fixed"; }
            String open()         { return "base open"; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-11s %-14s %-13s %-10s %s%n",
                "call", "instr", "target", "modifier", "overridable");
        for (Gauge.Call c : Gauge.constructorCalls(SOURCE, "BoundaryBase"))
            System.out.printf("%-11s %-14s %-13s %-10s %s%n",
                    c.name(), c.instruction(), c.target(), c.modifier(), c.overridable() ? "yes" : "no");
    }
}
```

```
call        instr          target        modifier   overridable
<init>      invokespecial  Object        -          no
restricted  invokevirtual  BoundaryBase  private    no
fixed       invokevirtual  BoundaryBase  final      no
open        invokevirtual  BoundaryBase  -          yes
```

The first line is a call never written in the source. `BoundaryBase`'s constructor has no
`super()` written in it, and yet an `Object` constructor call sits in the class file, as the
body's **first** instruction. The claim that the constructor chain runs top to bottom is read
right here: the chain's link was fastened by the compiler.

The real observation sits in the remaining three lines. All three calls compile to **the same
instruction**. The difference that could not be told apart in the source cannot be told apart
at the call site either: the instruction produced for `restricted`, `fixed`, and `open` is
byte-for-byte identical, and all three write their target as `BoundaryBase`. The call site
alone cannot say which one might reach a subclass.

The difference **sits in the target's modifier.** A private method is not inherited, a `final`
method cannot be overridden; in neither case is there a second implementation the virtual
machine could look up, so dynamic dispatch finds only one candidate even though it runs. Only
the unmarked method can have a second candidate — exactly what happens in the `Boundary` run.

This is the course's second claim, in its first instance: **the difference is invisible in
syntax.** Now one step further: the difference is not visible where the call is written
either. Which side the decision will come from is written not in the call, but in **the
declaration of the thing being called.**

#### Summary

- A name carries two types: the **declared type** the compiler sees, and the **runtime type**
  that is the object's real class. Every measurement in this course asks which one a decision
  comes from.
- The constructor chain runs top to bottom: base constructor, the subclass's field
  initializers, the subclass constructor's body, then delivery. Even when `super()` is not
  written, it sits in the class file and is the constructor's **first** instruction.
- An overridden method called from a constructor has its decision made by the **runtime
  type**: `Base`'s constructor writes `name()`, gets back **`sub method`**.
- The chosen method looks at fields not yet assigned: at that same point, `describe()` returns
  **`sub: null x0`**, because field initializers run only after the base constructor finishes.
- When a field is not just read but called on, setup falls with a **`NullPointerException`**
  and `new` produces no value at all: the reference stays **`null`**, and the half-built object
  is never delivered.
- The trap does not arise in a private or `final` method: all three calls compile to **the
  same instruction** in the class file and write the same target; the difference is carried
  not by the call site, but by what is called.

#### Next Step

In this lesson, every member between `Base` and `Sub` was open to the other; which method
could be seen was never asked. Yet in the boundary measurement, a modifier entered the picture
for the first time: a method marked `private` was not inherited by the subclass at all, and
left the decision to the declared type. What that single word closes off was not measured. The
next lesson asks who a name is seen **from**: four visibility levels are crossed with four
call positions, and how many of the sixteen pairs compile is counted. The measurement's second
half shows where visibility lives — it sits as a flag in the class file, while at runtime the
member keeps standing exactly where it was.

### Access Modifiers

Source: https://academia.sh/en/courses/java-object-model/access-modifiers

Crossing four visibility levels with four call positions, ten of sixteen pairs compile; the whole decision is made at compile time and the member stays right where it is at runtime — all four fields sit in the class file, and only a flag carries the distinction. Protected level also opens up the package.

The previous lesson measured the moment an object is constructed, and at its end a modifier
came out of nowhere: because a method marked `private` was never inherited by the subclass,
the constructor trap never arose there. That single word switched a decision's side, but the
word itself was never measured.

This lesson measures it. Its question is: who decides which side a member is **seen from**,
and when is that decision made? The answer is more definite here than this course tends to
give. Visibility is the most extreme case among the eight decisions the course measures: the
whole decision belongs to the **declared type** and to compile time — runtime contributes
nothing at all. The measurement shows this from two directions — by counting pairs that
compile and pairs that do not, then by looking at where the member sits at runtime.

#### Whose Question Is Visibility

Java recognizes four visibility levels, and three of them have a name: `private`,
`protected`, `public`. The fourth has no name — it is the level in effect when no modifier is
written at all, and it is called **package-private**. This namelessness is not a detail:
leaving a member unmarked is not failing to make a choice, it is choosing to leave it open to
the package.

Levels have no meaning on their own; meaning is born only together with **where access is
attempted from**. This is why the measurement is a crossing: each of the four levels is tried
from four separate positions. The positions are — inside the class declaring the member, an
unrelated class in the same package, a subclass in another package, an unrelated class in
another package. Sixteen pairs come out, and each pair reduces to a single question: does
this source compile?

The Object-Oriented Python and Types course established encapsulation as a **contract at the
name level**; visibility there was an agreement, and is not repeated here. The distinction
here is structural: the decision is made by the language's own compiler, and rejection is not
a warning, it is a source that does not compile.

#### The Measurement Core

The measurement runs no program at all. For every pair, a source set is produced, the compiler
is invoked from within the program, and only the exit status is read.

- **CI7** — The measurement's oracle is the rig itself: in every scenario the member is
  declared in exactly one place and read from exactly one place; there is no other reason for
  a compile failure.
- **CI8** — Every message the compiler produces is swallowed; the only thing measured is exit
  status. Why a source was rejected is read from the rig, not from the text.
- **CI9** — The same member is used in every scenario: `Base`'s `label` field. The only thing
  that changes across levels is that field's modifier.

```java
// Compile.java — compiles a source set; the result is only compiled or not compiled
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Compile {
    static boolean compiles(Map<String, String> files) throws Exception {
        Path d = Files.createTempDirectory("access");
        List<String> arg = new ArrayList<>(List.of("-d", d.resolve("out").toString()));
        for (var g : files.entrySet()) {
            Path p = d.resolve(g.getKey());
            Files.createDirectories(p.getParent());
            Files.writeString(p, g.getValue());
            arg.add(p.toString());
        }
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, arg.toArray(String[]::new)) == 0;
    }

    static List<String> fieldFlags(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("flags");
        Path f = d.resolve(className + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), f.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        List<String> lines = new ArrayList<>();
        for (FieldModel fm : ClassFile.of().parse(d.resolve(className + ".class")).fields()) {
            var flags = fm.flags();
            lines.add(fm.fieldName().stringValue() + " " + (flags.has(AccessFlag.PRIVATE) ? "ACC_PRIVATE"
                    : flags.has(AccessFlag.PROTECTED) ? "ACC_PROTECTED"
                    : flags.has(AccessFlag.PUBLIC) ? "ACC_PUBLIC" : "(no flag)"));
        }
        return lines;
    }
}
```

#### How Many of Sixteen Pairs Compile

```java
// Access.java — four visibility levels x four call positions
import java.util.*;

public class Access {
    static final String[] LEVELS = { "private", "", "protected", "public" };
    static final String[] LEVEL_NAMES = { "private", "(default)", "protected", "public" };
    static final String FORMAT = "%-13s %-13s %-13s %-13s %s%n";

    static String base(String level) {
        return "package home;\npublic class Base {\n"
                + "    " + level + " String label = \"base field\";\n"
                + "    public String own() { return label; }\n}\n";
    }

    static Map<String, String> scenario(String level, int position) {
        Map<String, String> d = new LinkedHashMap<>();
        d.put("home/Base.java", base(level));
        switch (position) {
            case 1 -> d.put("home/Neighbor.java",
                    "package home;\nclass Neighbor { String read(Base u) { return u.label; } }\n");
            case 2 -> d.put("away/OutsideSub.java", "package away;\nimport home.Base;\n"
                    + "public class OutsideSub extends Base { public String read() { return this.label; } }\n");
            case 3 -> d.put("away/Outsider.java", "package away;\nimport home.Base;\n"
                    + "class Outsider { String read(Base u) { return u.label; } }\n");
            default -> { }
        }
        return d;
    }

    public static void main(String[] args) throws Exception {
        System.out.printf(FORMAT, "level", "same class", "same package", "subclass", "outside pkg");
        int compiledCount = 0;
        for (int i = 0; i < LEVELS.length; i++) {
            String[] results = new String[4];
            for (int j = 0; j < 4; j++) {
                boolean ok = Compile.compiles(scenario(LEVELS[i], j));
                results[j] = ok ? "compiled" : "not compiled";
                if (ok) compiledCount++;
            }
            System.out.printf(FORMAT, LEVEL_NAMES[i], results[0], results[1], results[2], results[3]);
        }
        System.out.println("compiled count of 16 pairs: " + compiledCount);
    }
}
```

```
level         same class    same package  subclass      outside pkg
private       compiled      not compiled  not compiled  not compiled
(default)     compiled      compiled      not compiled  not compiled
protected     compiled      compiled      compiled      not compiled
public        compiled      compiled      compiled      compiled
compiled count of 16 pairs: 10
```

**Ten** of sixteen pairs compile, six do not. The table's shape is a staircase: every row opens
what the previous one opened and adds one more position on top. Levels, for this reason, are
not four separate rules, they are **four nested rings**; moving a member from one level to the
next opens a new door and closes none.

The first column is the same across all four rows. A class sees its own member at every level;
even `private` closes nothing inside the class itself. Visibility does not hide a member from
its owner, it bounds **the calling side**.

The only difference between the first and second row is the absence of a single word, and that
absence lets in every class in the same package. An unmarked field can be read from the
`Neighbor` class; `Neighbor` carries no inheritance relationship to `Base` at all, it merely
sits in the same package. In this table, a package is not an organizational unit, it is an
**access boundary**.

What the last row says is that the fourth column fills for the first time. `public` is the
only level opening outside the package declaring the member; the remaining three are bound to
the package to some degree or other.

All of these decisions are made in one single place: the compiler. In none of the sixteen
scenarios did a program run. For six of the pairs there is not even a class file — the defect
shows up not as an error in a running program, but as **a program that never came to exist**.
Among the eight decisions this course measures, visibility is the extreme where the runtime
type's share is zero.

#### A Bounding Measurement: Protected Level Also Opens the Package

One row in the table resists the first reading. `protected` is mostly read as "open only to
subclasses"; yet the third row's second column says **compiled**. A class that is not a
subclass, and merely sits in the same package, can read the protected field.

This single measurement does more than bound a rule — it also reshapes what `protected` is.
To see the detail, the same protected field is tried from five separate places.

- **CI10** — In all five trials, the `Base` class and the `label` field are byte-for-byte
  identical; what changes is only the trying class's package, its inheritance relationship,
  and the **declared type of the reference** it uses.

```java
// Protection.java — what protected level actually opens
import java.util.*;

public class Protection {
    static final String BASE = "package home;\npublic class Base {\n"
            + "    protected String label = \"base field\";\n}\n";
    static final String PREFIX = "package away;\nimport home.Base;\n";

    static final String[][] TRIALS = {
        { "same package, unrelated class", "home/Neighbor.java",
          "package home;\nclass Neighbor { String read(Base u) { return u.label; } }\n" },
        { "outside package, subclass, via this", "away/A.java",
          PREFIX + "public class A extends Base { String read() { return this.label; } }\n" },
        { "outside package, subclass, via own type", "away/B.java",
          PREFIX + "public class B extends Base { String read(B b) { return b.label; } }\n" },
        { "outside package, subclass, via Base type", "away/C.java",
          PREFIX + "public class C extends Base { String read(Base u) { return u.label; } }\n" },
        { "outside package, unrelated class", "away/D.java",
          PREFIX + "class D { String read(Base u) { return u.label; } }\n" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-44s %s%n", "access site", "result");
        for (String[] d : TRIALS) {
            Map<String, String> k = new LinkedHashMap<>();
            k.put("home/Base.java", BASE);
            k.put(d[1], d[2]);
            System.out.printf("%-44s %s%n", d[0], Compile.compiles(k) ? "compiled" : "not compiled");
        }
    }
}
```

```
access site                                  result
same package, unrelated class                compiled
outside package, subclass, via this          compiled
outside package, subclass, via own type      compiled
outside package, subclass, via Base type     not compiled
outside package, unrelated class             not compiled
```

**Three** of five trials compile. The first row is the bounding measurement itself: a
protected member is open to the **whole package**, and inheritance is not required for that.
The sentence "protected means open only to subclasses" is disproved by the measurement.

The difference between the third and fourth rows is the most overlooked side of
`protected`. Both classes are `Base`'s subclass, both sit in an outside package, both try to
read the same field. The distinction is in **which reference the read is made through**: `B`
uses a reference of its own type and compiles, `C` uses a reference whose declared type is
`Base` and does not compile. The object can be the same object; the decision is made by
looking not at the object, but at the reference's **declared type**.

This is the lesson's opening claim in its sharpest form. Same field, same subclass, same line
shape — and two separate outcomes. The only thing producing the difference is the type the
compiler sees while resolving that name. A subclass in an outside package can use a protected
member **through its own line of inheritance**, not as a general tool of the base type.

#### Where the Member Sits

Do accesses rejected at compile time have any runtime counterpart at all? The question is
answered by looking at what state the member is in inside the class file.

```java
// Flag.java — what four levels leave in the class file
public class Flag {
    static final String SOURCE = """
        class Member {
            private   String hidden  = "";
                      String shared  = "";
            protected String guarded = "";
            public    String open    = "";
        }
        """;

    public static void main(String[] args) throws Exception {
        var fields = Compile.fieldFlags(SOURCE, "Member");
        System.out.println("field count in class file: " + fields.size());
        for (String s : fields) System.out.println("  " + s);
    }
}
```

```
field count in class file: 4
  hidden ACC_PRIVATE
  shared (no flag)
  guarded ACC_PROTECTED
  open ACC_PUBLIC
```

All four of the four fields are in the class file. The `private`-declared field has not been
deleted, hidden, or moved anywhere else; it sits in the same list, in the same form, as the
others. The only thing separating them is a **flag**.

Package level's counterpart shows here too. Each of the other three levels has a name in the
class file; the unmarked field carries no flag at all. The namelessness in the source stays
nameless in the class file too: package level is the absence of any of the three flags being
set.

This is where the lesson's second half comes from. Visibility is not a **hiding mechanism**;
it does not put a member where no one can reach it. What it does is tell the compiler which
sources to reject. Encapsulation is not a barrier, it is a contract — and that contract's
enforcement shows up not in a running program, but in a program that never gets born at all.

#### In Whose Compile Does the Defect Show

Narrowing a level is changing a single word in its owner's source. Who this change hits can be
measured: the same `Base` class is compiled at two levels, first alone, then together with a
caller in an outside package.

```java
// Cost.java — when a level is narrowed, whose compile does the defect break
import java.util.*;

public class Cost {
    static final String CALLER = "package away;\nimport home.Base;\n"
            + "class Outsider { String read(Base u) { return u.label; } }\n";

    static String base(String level) {
        return "package home;\npublic class Base {\n    " + level
                + " String label = \"base field\";\n}\n";
    }

    public static void main(String[] args) throws Exception {
        for (String level : new String[] { "public", "protected" }) {
            Map<String, String> both = new LinkedHashMap<>();
            both.put("home/Base.java", base(level));
            both.put("away/Outsider.java", CALLER);
            System.out.printf("%-10s | Base alone: %-13s | with caller: %s%n", level,
                    Compile.compiles(Map.of("home/Base.java", base(level))) ? "compiled" : "not compiled",
                    Compile.compiles(both) ? "compiled" : "not compiled");
        }
    }
}
```

```
public     | Base alone: compiled      | with caller: compiled
protected  | Base alone: compiled      | with caller: not compiled
```

The middle column says **compiled** on both rows. `Base`'s source is flawless at protected
level too; it compiles on its own, a class file is produced, no warning is raised. Nothing
visible shows on the owner's side.

The right column, though, splits. The same `Outsider` class, the same line, two separate
outcomes — and nothing changed in `Outsider`'s own source. The defect is born not in the
compile of the class that made the change, but in **the compile of the class looking at it**.

This is this lesson's answer to the "where does the cost show" question every lesson in this
course asks. Widening visibility is retroactively safe: it opens a new door and closes none
that were open. Narrowing produces a break somewhere the owner cannot see. The cost of
declaring a member `public` is the cost of later taking that back.

#### Summary

- Crossing four visibility levels with four call positions, **ten** of sixteen pairs compile;
  the table is shaped like four nested rings, each level adding one position atop the last.
- Visibility does not hide a member from its owner — the first column compiles at all four
  levels — what is bounded is **the calling side**; the unmarked level is not a non-choice
  either, it lets in unrelated classes in the same package and turns the package into an
  access boundary.
- Protected level also opens up the package: a neighbor class that is not a subclass can read
  a protected field, so "open only to subclasses" is the wrong reading.
- A subclass in an outside package can reach a protected member through a reference **of its
  own type**, not through a reference whose declared type is the base class: the decision
  looks not at the object, but at the reference's declared type.
- All four of the four fields sit in the class file; only a flag carries the distinction, and
  package level carries no flag. Visibility is not hiding, it is a rule handed to the compiler.
- When a level is narrowed, the owner's source still compiles; the defect is born in **the
  caller's compile**. Widening is retroactively safe, narrowing breaks somewhere else.

#### Next Step

For two lessons the same pair has been used, and each measured one side of the decision: the
runtime type chose the method called from the constructor, the declared type decided
accessibility. The two were never set side by side. The next lesson is this course's core
measurement: eight separate decisions are read on a single reference, and how many of them
came from which side is counted. On the same line, `d.name()` and `d.label` give two opposing
answers — redeclaring fields under the same name is not overriding, it is **hiding**, and a
hidden field does not vanish. In the same lesson, what the `@Override` mark catches at compile
time, and why the decision switches sides when a signature changes, are measured.

### Inheritance and Overriding

Source: https://academia.sh/en/courses/java-object-model/inheritance-and-overriding

Eight decisions are read on a single reference and the answer splits right down the middle: four come from the runtime type, four from the declared type. Redeclaring fields under the same name is not overriding, it is hiding; once the signature changes, overriding never arises at all and the decision switches sides.

For two lessons, one side of the decision was measured at a time. The **runtime type** picked
the overridden method called from a constructor; the **declared type** decided whether a
member was visible at all. The two were never set side by side, so the rule between them was
never asked either.

This lesson is the course's core measurement. A single reference is set up — declared type
`Base`, runtime type `Sub` — and eight separate language decisions are read through that
reference. For each decision, one question is asked: which type gave the result? The
measurement's oracle is the rig itself; since we deliberately separated the two types, every
answer's source is known from the start.

The Programming Paradigms course measured inheritance's two promises and how the subtype
contract gets violated; what was counted there was **contract violation**. What is counted
here is **which side the decision comes from**, and the measurement comes from an actual run.

#### The Measurement Core

The core is made of three types: an interface (`Carrier`), a base class (`Base`), and a
subclass (`Sub`) extending it. `Sub` **overrides** `Base`'s method, and **redeclares** its
field under the same name — that the two are separate things is this lesson's second
measurement.

- **CI11** — Every member returns its own origin as text (`base field`, `sub method`); the
  returned value itself says which declaration it came from, no inference is needed.
- **CI12** — Seven of the eight decisions are read through the **same** reference; the static
  method is called directly on the class, since the declared type already gives the answer.
  What changes is the kind of member being read.
- **CI13** — The measurement reads no environment-dependent data: identity hash, duration, and
  path are not written; identity, where needed, is compared with `==`.

```java
// Base.java — the course's core: an interface, a base class, a subclass extending it
interface Carrier {
    default String carry() { return "interface default"; }
}

class Base implements Carrier {
    String label = "base field";
    String name() { return "base method"; }
    String match(Base u) { return "Base.match(Base)"; }
    static String status() { return "base static"; }
    @Override public String toString() { return "Base object"; }
}

class Sub extends Base {
    String label = "sub field";
    @Override String name() { return "sub method"; }
    String match(Sub a) { return "Sub.match(Sub)"; }
    static String status() { return "sub static"; }
    @Override public String carry() { return "sub override"; }
    @Override public String toString() { return "Sub object"; }
}
```

Two measurements in this lesson read not a run but a **compile**: in one, whether a source is
accepted; in the other, the fields counted in the class file produced. Both come from the same
helper.

```java
// Compile.java — compiles a source; gives either the compile result or a class's fields
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Compile {
    static Path compile(String source) throws Exception {
        Path d = Files.createTempDirectory("inherit");
        Path f = d.resolve("Trial.java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), f.toString()) == 0 ? d : null;
    }

    static String outcome(String source) throws Exception {
        return compile(source) == null ? "not compiled" : "compiled";
    }

    static List<String> fields(String source, String className) throws Exception {
        Path d = compile(source);
        List<String> names = new ArrayList<>();
        for (FieldModel f : ClassFile.of().parse(d.resolve(className + ".class")).fields())
            names.add(f.fieldName().stringValue());
        return names;
    }
}
```

#### Eight Decisions, One Reference

```java
// Decision.java — eight decisions on a single reference
public class Decision {
    static String pick(Base u) { return "Base signature"; }
    static String pick(Sub a) { return "Sub signature"; }

    static void report(String decision, String result, String decider) {
        System.out.printf("%-24s %-22s %s%n", decision, result, decider);
    }

    public static void main(String[] args) {
        Base d = new Sub();
        System.out.println("declared type Base, runtime type "
                + d.getClass().getSimpleName());
        System.out.println();
        System.out.printf("%-24s %-22s %s%n", "decision", "result", "decided by");
        report("overridden method", d.name(), "runtime type");
        report("interface default", d.carry(), "runtime type");
        report("toString conversion", d.toString(), "runtime type");
        report("field read", d.label, "declared type");
        report("overload pick", pick(d), "declared type");
        report("static method", Base.status(), "declared type");
        report("type test", String.valueOf(d instanceof Sub), "runtime type");
        report("downcast", ((Sub) d).label, "declared type (after cast)");
    }
}
```

```
declared type Base, runtime type Sub

decision                 result                 decided by
overridden method        sub method             runtime type
interface default        sub override           runtime type
toString conversion      Sub object             runtime type
field read               base field             declared type
overload pick            Base signature         declared type
static method            base static            declared type
type test                true                   runtime type
downcast                 sub field              declared type (after cast)
```

**Four** of eight decisions come from the runtime type, **four** from the declared type. The
sentence "Java is object-oriented, a call goes to the real object" explains only half of the
decisions.

The split is not arbitrary. The top three rows plus the seventh concern **behavior**: which
body runs, what the object actually is. These look at the runtime type, because the answer can
only be known once the object exists. The remaining four are **name resolution**: which field
gets read, which signature gets picked, which class's static method gets called. These end at
the compiler's desk, and the compiler has only the declared type in hand.

The rule fits in one sentence: **the behavior called looks at the runtime type, the name
picked looks at the declared type.** The second row is this rule's least visible instance — a
default method coming from an interface can be overridden too, and its decision also comes
from the runtime type.

The last row adds a fourth reading. `((Sub) d).label` gives **sub field**, even though the
object has been the same object all along. A downcast does not change the object, it changes
the **declared type**; it is a promise given to the compiler, and the promise's truth is
tested at runtime.

#### Separating Overriding from Hiding

The fourth row is this lesson's quietest result. `d.name()` gives **sub method** while
`d.label` gives **base field**; both are written through the same `d`, with a two-character
difference. Redeclaring fields under the same name is not overriding; it is **hiding**. To see
what the hidden field is, two separate references are bound to a single object.

```java
// Hiding.java — the hidden field does not vanish
public class Hiding {
    public static void main(String[] args) {
        Base u = new Sub();
        Sub a = (Sub) u;
        System.out.println("are the two references the same object: " + (u == a));
        System.out.println("label via Base reference   : " + u.label);
        System.out.println("label via Sub reference    : " + a.label);
        System.out.println("name() via Base reference  : " + u.name());
        System.out.println("name() via Sub reference   : " + a.name());
        a.label = "written";
        System.out.println("u.label after writing a.label: " + u.label);
    }
}
```

```
are the two references the same object: true
label via Base reference   : base field
label via Sub reference    : sub field
name() via Base reference  : sub method
name() via Sub reference   : sub method
u.label after writing a.label: base field
```

The first line is the measurement's foundation: there is exactly one object. Yet that single
object gives **two separate answers** to the `label` question, and **one answer** to the
`name()` question. The overridden method replaces the one above it; the redeclared field does
not replace the one above it, **it sits on top of it.**

The last line is this in memory terms. After `a.label` is written to, `u.label` still says
**base field**. There are two separate places inside the same object, and writing one does not
change the other. The hidden field has not vanished; it has only become invisible from a
reference whose declared type is `Sub`.

That the two places are truly two places can also be read from the class file. The same pair
is compiled and the fields each class declares are listed.

```java
// Location.java — where the hidden field sits in the class file
public class Location {
    static final String SOURCE = """
        class Base { String label = "base field"; String name() { return "base method"; } }
        class Sub extends Base { String label = "sub field";
                                @Override String name() { return "sub method"; } }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("fields in Base's class file: " + Compile.fields(SOURCE, "Base"));
        System.out.println("fields in Sub's class file: " + Compile.fields(SOURCE, "Sub"));
    }
}
```

```
fields in Base's class file: [label]
fields in Sub's class file: [label]
```

Two separate class files, one `label` declaration in each. The overridden `name()` method
also has two bodies, but a call only ever goes to one of them; a field has no such choice —
both stay readable. Inheritance **replaces one with the other** for a method, and **leaves the
two standing side by side** for a field.

This is where the cost is read. A programmer writing a field under the same name as the one
above it in a subclass usually believes they have changed the field above. They have not —
both fields go on living, and which one is read is decided by looking at the declared type of
the reference on that line. The defect is invisible where it is written; it shows where it is
**read**, silently returning the wrong value.

#### What the Compiler Catches and Misses

Overriding is not a declaration; it arises on its own whenever two signatures line up
exactly. For this reason, its **not** arising is automatic too, and silent. The `@Override`
mark exists precisely to break that silence: it makes the compiler answer, for the subclass,
the question "is this really an override?"

- **CI14** — In all five trials, the base class is byte-for-byte identical; the only thing
  that changes is the subclass's single-line declaration. What is measured is only the
  compile result.

```java
// Mark.java — what @Override catches, how far a signature can drift
public class Mark {
    static final String ROOT = """
        class A { String name() { return "base"; } A make() { return this; }
                  String match(A a) { return "A"; } }
        """;

    static final String[][] TRIALS = {
        { "unmarked, name misspelled", "class B extends A { String naem() { return \"sub\"; } }" },
        { "marked, name misspelled", "class B extends A { @Override String naem() { return \"sub\"; } }" },
        { "marked, return type narrowed", "class B extends A { @Override B make() { return this; } }" },
        { "marked, return type widened", "class B extends A { @Override Object make() { return this; } }" },
        { "marked, parameter type narrowed", "class B extends A { @Override String match(B b) { return \"B\"; } }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-38s %s%n", "subclass", "result");
        for (String[] d : TRIALS)
            System.out.printf("%-38s %s%n", d[0], Compile.outcome(ROOT + d[1] + "\n"));
    }
}
```

```
subclass                               result
unmarked, name misspelled              compiled
marked, name misspelled                not compiled
marked, return type narrowed           compiled
marked, return type widened            not compiled
marked, parameter type narrowed        not compiled
```

The first two rows carry the mark's whole value. A method with a misspelled name **compiles**
when no mark is placed: as far as the compiler is concerned there is no defect, a new method
has only been added to the subclass. The program runs, no warning is raised, and the base
class's version keeps getting called. The same source does **not compile** with `@Override`.
The mark adds no behavior; it only states an assumption to the compiler, and rejects the
source when that assumption does not hold.

The third and fourth rows measure how far a return type is allowed to drift. An overriding
method can **narrow** its return type — if the one above returns `A`, the one below can return
`B` — but cannot widen it. The reason is the subtype contract: when a caller expecting `A`
gets a `B`, the contract is not broken; when it gets an `Object`, it is. Narrowing strengthens
the promise given to the caller, widening weakens it.

The fifth row opens the door to the next section. When the parameter type is narrowed, the
marked source does **not compile**: there is no override in play at all. The narrowing that is
free in a return type is forbidden in a parameter — and the consequence of this is not only a
compile error.

#### A Bounding Measurement: When the Signature Changes, the Decision Switches Sides

When `@Override` is not written, the same declaration compiles. What happens then? The
`match` pair added to the core is exactly this case: `Base` takes a `Base`, `Sub` takes a
`Sub`, the signatures do not line up, and so what exists is not overriding but **overloading**.

- **CI15** — In both calls the object is the same object and the argument is the same
  argument; the only thing that changes is the declared type of the reference the call is
  made through.

```java
// Signature.java — when the signature changes, overload replaces overriding
public class Signature {
    public static void main(String[] args) {
        Base d = new Sub();
        Sub real = new Sub();
        System.out.println("match via Base reference : " + d.match(real));
        System.out.println("match via Sub reference  : " + ((Sub) d).match(real));
        System.out.println("name() via Base reference: " + d.name());
        System.out.println("name() via Sub reference : " + ((Sub) d).name());
    }
}
```

```
match via Base reference : Base.match(Base)
match via Sub reference  : Sub.match(Sub)
name() via Base reference: sub method
name() via Sub reference : sub method
```

The four lines are read in pairs. The bottom two are overriding's signature: two separate
references, **one answer**. Changing the reference's declared type does not change the
result, because it is not the side giving the decision.

The top two, in turn, are overloading's signature: same object, same argument, **two separate
answers**. Two calls that could be written without changing a single character go to separate
bodies for no reason but the reference's declared type. The decision has switched sides.

This is what the boundary means. A programmer writing a method in a subclass usually believes
they are overriding; if a letter or a parameter type slips in the signature, overriding never
arises at all, and in its place comes a choice that never listens to the runtime type. The
program compiles, runs, and calls the base class's version. This is why `@Override` is not a
comment added to the source — it is the only way to freeze, at compile time, which side a
decision will come from.

#### Summary

- Eight decisions are read on a single reference and the answer splits down the middle:
  **four** come from the runtime type, **four** from the declared type.
- The rule is single: **the behavior called** looks at the runtime type, **the name picked**
  looks at the declared type. A default method from an interface can be overridden too and
  falls into the first group.
- A downcast does not change the object, it changes the declared type: `((Sub) d).label` gives
  **sub field**, while the object has been the same object all along.
- Redeclaring fields under the same name is **hiding**: a single object gives two answers to
  the `label` question, and writing one does not change the other — the hidden field does not
  vanish.
- `@Override` adds no behavior, it states an assumption: a method with a misspelled name
  **compiles** unmarked, does **not compile** marked. The return type can be narrowed, not
  widened.
- When the signature does not line up, overriding never arises, it becomes overloading, and
  the decision switches sides: `match` gives two separate answers from two references while
  `name()` gives one.

#### Next Step

In this lesson, `Base` carried a body: every method had an implementation, and the subclass
overrode them if it chose to. The default method from the interface entered the table as one
row too, but what the interface itself is for was never asked. The next lesson sets two tools
of abstraction side by side: a capability can be established with an abstract class as well as
an interface, one is bound to a single supertype, the other to many. The measurement looks at
the case where two interfaces give the same default method — in how many cases does the
compiler refuse to decide, in how many does one side silently win — and shows why a capability
carrying state cannot be established with an interface.

### Abstract Classes and Interfaces

Source: https://academia.sh/en/courses/java-object-model/abstract-classes-and-interfaces

The same capability is set up eight separate ways, and compilation stops on three of them; on the remaining five the compiler decides on its own and never asks which side — a class body beats an interface default, a subinterface beats the one above it, and only two independent interfaces giving the same default get rejected. A state-carrying capability cannot be set up with an interface.

The previous lesson read eight decisions on a single reference, and the table's second row
came from an interface: `Carrier`'s default method could also be overridden, and its decision
also came from the runtime type. The interface itself only passed through as one row.

This lesson makes it the subject. In Java, a capability can be set up with two separate
tools: an **abstract class** with an incomplete body, or an **interface**. The two look like
they do the same job, and the choice is usually told as a design preference. The measurement
looks at something else: **which setup does the compiler reject, and when it does not, who
does it leave the decision to?**

The Programming Paradigms course compared the choice between interface and abstract class
with design reasoning; that reasoning is not repeated here. What is counted here is where the
compiler stops.

#### Two Tools, Two Separate Constraints

An abstract class is a class: it carries fields, has a constructor, can place bodied and
bodyless methods side by side. In exchange, it is bound to Java's single inheritance — a
class can extend only one class.

An interface does the reverse. A class can implement as many interfaces as it likes, so
capabilities can be attached to a class side by side. In exchange, an interface **cannot hold
instance state**: field declarations are implicitly constant. Default methods give an
interface the ability to carry a body, but not a state to run on.

Put these two constraints together and a question is born. If multiple implementation is
free, what happens when two separate interfaces give a bodied method under **the same
signature**? This situation never arises in inheritance, because there is only ever one
superclass. The measurement looks right here.

#### The Measurement Core

The measurement's first half runs no program at all: every setup is a source text, the
compiler is invoked from within the program, and only the exit status is read.

- **CI16** — All eight setups establish the same capability: a single method named `carry`,
  returning a string. The only thing that changes is which type form the capability is given
  through.
- **CI17** — Every message the compiler produces is swallowed; the only thing read is whether
  the source is accepted.

```java
// Compile.java — says whether the given source compiles
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Compile {
    static String outcome(String source) throws Exception {
        Path d = Files.createTempDirectory("abstract");
        Path f = d.resolve("Trial.java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), f.toString()) == 0
                ? "compiled" : "not compiled";
    }
}
```

#### Eight Setups, How Many Compile

```java
// Setup.java — the same capability set up eight separate ways, how many compile
public class Setup {
    static final String[][] TRIALS = {
        { "extending a single abstract class",
          "abstract class A { abstract String carry(); }\nclass C extends A { public String carry() { return \"c\"; } }" },
        { "extending two abstract classes",
          "abstract class A { }\nabstract class B { }\nclass C extends A, B { }" },
        { "not implementing the abstract method",
          "abstract class A { abstract String carry(); }\nclass C extends A { }" },
        { "implementing two interfaces",
          "interface A { default String carry() { return \"a\"; } }\ninterface B { default String name() { return \"b\"; } }\nclass C implements A, B { }" },
        { "two interfaces, same default, unresolved",
          "interface A { default String carry() { return \"a\"; } }\ninterface B { default String carry() { return \"b\"; } }\nclass C implements A, B { }" },
        { "two interfaces, same default, class resolves",
          "interface A { default String carry() { return \"a\"; } }\ninterface B { default String carry() { return \"b\"; } }\nclass C implements A, B { public String carry() { return \"c\"; } }" },
        { "interface and abstract class, same method",
          "interface A { default String carry() { return \"a\"; } }\nabstract class S implements A { public String carry() { return \"s\"; } }\nclass C extends S { }" },
        { "subinterface overrides the one above",
          "interface A { default String carry() { return \"a\"; } }\ninterface B extends A { default String carry() { return \"b\"; } }\nclass C implements B { }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-46s %s%n", "setup", "result");
        int stopped = 0;
        for (String[] d : TRIALS) {
            String s = Compile.outcome(d[1] + "\n");
            if (s.equals("not compiled")) stopped++;
            System.out.printf("%-46s %s%n", d[0], s);
        }
        System.out.println("setups where compilation stopped: " + stopped + " / " + TRIALS.length);
    }
}
```

```
setup                                          result
extending a single abstract class              compiled
extending two abstract classes                 not compiled
not implementing the abstract method           not compiled
implementing two interfaces                    compiled
two interfaces, same default, unresolved       not compiled
two interfaces, same default, class resolves   compiled
interface and abstract class, same method      compiled
subinterface overrides the one above           compiled
setups where compilation stopped: 3 / 8
```

Compilation stops on **three** of eight setups, and continues on **five**. The three stopping
ones stop for separate reasons, and all three define this lesson's two tools.

The second row is the abstract class's constraint: extending two classes at once is not even
writable. Single inheritance is not a design suggestion, it is a spelling the language
rejects. The third row is the abstract class's contract: when a method left bodyless is not
filled in by the subclass, that class cannot be concrete. An abstract class declares a gap and
forces that gap to be closed at compile time.

The fifth row is the interface's counterpart, and this lesson's core measurement. When two
independent interfaces give a bodied method under the same signature, the compiler **refuses
to decide.** There is no ambiguity here to resolve — both candidates sit at equal distance, and
rather than defining a priority between them, the language sends the source back. This never
arises with inheritance; this is exactly the cost of multiple implementation.

The sixth row shows how the cost is paid. Same two interfaces, same clash — but once the class
writes its own body, the source **compiles**. What the compiler wants is not to pick one of
the candidates; it wants the choice **written in the source.**

The two simplest of the compiling rows say why the two tools sit side by side. The first row
is the abstract class's: once the method left incomplete is filled in by the subclass, the
class becomes concrete and the chain is set up. The fourth is the interface's: class `C`
takes on two separate capabilities at once, and since neither signature clashes, there is no
question at all. A clash is not a rule, it is a **case of intersection**; it is only born when
two independent interfaces give the same signature. The rest of the measurement counts how
that intersection gets closed.

#### Who Wins When Compilation Does Not Stop

Three of the remaining five setups have more than one candidate present, and compilation
still continues. In these cases, who makes the decision?

- **CI18** — In all four setups, the call is made through a reference whose declared type is
  `Carrier`; the only thing that changes is the type hierarchy building the runtime type.
- **CI19** — Every body returns its own origin as text; the winning side is read from the
  returned value, not by inference.

```java
// Carrier.java — types giving the same capability two separate ways
interface Carrier {
    default String carry() { return "interface default"; }
}

interface Labeled extends Carrier {
    @Override default String carry() { return "subinterface default"; }
}

interface Second {
    default String carry() { return "second interface default"; }
}

abstract class AbstractCarrier implements Carrier {
    @Override public String carry() { return "abstract class body"; }
    abstract String name();
}

class Specific implements Labeled { }

class ClassWins extends AbstractCarrier implements Carrier {
    @Override String name() { return "class"; }
}

class Resolver implements Carrier, Second {
    @Override public String carry() { return "class resolved it itself"; }
}

class Picker implements Carrier, Second {
    @Override public String carry() { return Second.super.carry(); }
}
```

```java
// Winner.java — which side wins when compilation does not stop
public class Winner {
    public static void main(String[] args) {
        Carrier a = new Specific();
        Carrier b = new ClassWins();
        Carrier c = new Resolver();
        Carrier e = new Picker();
        System.out.printf("%-44s %s%n", "source", "carry() result");
        System.out.printf("%-44s %s%n", "interface, super and sub", a.carry());
        System.out.printf("%-44s %s%n", "interface default and class body", b.carry());
        System.out.printf("%-44s %s%n", "two independent interfaces, class resolves", c.carry());
        System.out.printf("%-44s %s%n", "two independent interfaces, class picks", e.carry());
    }
}
```

```
source                                       carry() result
interface, super and sub                     subinterface default
interface default and class body             abstract class body
two independent interfaces, class resolves   class resolved it itself
two independent interfaces, class picks      second interface default
```

Four lines, four separate choices, and only the last two are written in the source.

The first line has two interfaces, but one extends the other. This is not a clash: `Labeled`
**overrides** `Carrier`'s default, and the more specific one wins. The rule is the same one
inheritance uses; when there is a sub–super relationship between two types, the one below is
picked.

The second line is the language's silent priority. `ClassWins` gets a body both from an
abstract class and from an interface default. No choice is written in the source, yet
compilation continues and the result comes out **abstract class body**. A body coming from the
class line always beats an interface default. This rule resolves an ambiguity, but asks no
one about it — whoever wrote the interface can never see, from their own source, that their
body never gets called.

The third line is the fifth setup's compiling form. `Resolver` implements two independent
interfaces and writes its own body; the winner is **whichever side is written in the source.**
Here the clash is not resolved, it is merely stepped over: neither of the two default bodies
ever gets called.

The fourth line is the clash's real resolution. `Picker` also writes its own body, but inside
that body it calls one of the candidates **by name** — the result is **second interface
default**. What the compiler wanted was not new behavior; it wanted **which candidate was
picked to be written down**, and the language gives a form for writing that choice. The
difference between a rejected source and an accepted one is not an implementation, it is a
signature.

From here comes the lesson's main reading. The compiler shows three separate behaviors: if a
hierarchy exists, it picks the most specific one; when a class meets an interface, it picks
the class; when two equal interfaces meet, it picks nothing. Only the last is visible to the
programmer. The first two are decisions too, and both are made **at compile time**, by looking
at the hierarchy of declared types; the runtime type's share here is limited not to deciding
which body gets called, but to deciding what class the body-carrying object belongs to.

#### A Bounding Measurement: A State-Carrying Capability Cannot Be Set Up with an Interface

The table makes the interface look more flexible because of multiple implementation. The
boundary sits in what an interface cannot hold.

- **CI20** — All three setups establish the same capability: a single operation incrementing
  a counter. The only thing that changes is where the counter is held.

```java
// State.java — which tool can a state-carrying capability be set up with
public class State {
    static final String[][] TRIALS = {
        { "constant declared in interface",
          "interface Counter { int START = 0; }" },
        { "instance field and increment in interface",
          "interface Counter { int value = 0; default void increment() { value++; } }" },
        { "field and increment in abstract class",
          "abstract class Counter { int value = 0; void increment() { value++; } }" },
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-44s %s%n", "how the capability is set up", "result");
        for (String[] d : TRIALS)
            System.out.printf("%-44s %s%n", d[0], Compile.outcome(d[1] + "\n"));
        System.out.println();
        Tally x = new Tally(), y = new Tally();
        x.increment(); x.increment(); y.increment();
        System.out.println("two instances derived from the abstract class: " + x.value + " and " + y.value);
        System.out.println("the interface's constant reads without an instance: " + Constant.START);
    }
}

abstract class AbstractCounter {
    int value = 0;
    void increment() { value++; }
}

class Tally extends AbstractCounter { }

interface Constant { int START = 0; }
```

```
how the capability is set up                 result
constant declared in interface               compiled
instance field and increment in interface    not compiled
field and increment in abstract class        compiled

two instances derived from the abstract class: 2 and 1
the interface's constant reads without an instance: 0
```

The first row is misleading: a field **can** be declared in an interface. The second row says
what it actually is — it cannot be incremented, because it is implicitly constant. Every field
declaration written in an interface, even unmarked, is class-level and unchangeable. As the
last line shows, it reads without any instance ever being constructed, meaning it does not
belong to an instance at all.

The third row and the run below it measure the opposite side. The same capability compiles
when set up with an abstract class, and two instances count **2** and **1**: each object has
its own counter. What the capability needed was not a body, it was **a place belonging to an
instance** for that body to work on, and an interface cannot give that place. The two numbers
coming out separate is the measurement itself: had a single place been shared, both
references would read **3**.

The boundary's meaning reverses the flexibility of this lesson's first half. An interface
looks broader because it can attach in multiples; but what it can actually carry is only
behavior. If a capability holds state, multiple implementation cannot even be asked about,
because there is only one candidate. The choice between the two tools is not a style question:
if the capability is stateless, both paths are open and an interface earns multiple
attachment; if it carries state, there is only one path, and taking it also brings single
inheritance's constraint along.

#### Summary

- Compilation stops on **three** of eight setups of the same capability: extending two
  classes at once, not filling in an abstract method, and two independent interfaces giving
  the same default.
- When two independent interfaces give a body under the same signature, the compiler
  **refuses to decide**; the same source compiles once the class writes its own body. What is
  wanted is not new behavior, it is the choice written in the source — a class can also call
  one candidate by name.
- When compilation does not stop, the decision is still the compiler's, and it is silent: if a
  hierarchy exists, the **most specific** interface wins; when a class meets an interface, the
  **class body** wins.
- The cost of the silent decision is invisibility: whoever wrote the interface default cannot
  see, from their own source, that their body never gets called.
- A field declared in an interface is implicitly constant and reads without an instance; when
  incrementing is attempted, the source **does not compile**.
- The same counter set up with an abstract class counts **2** and **1** across two instances:
  a state-carrying capability cannot be set up with an interface, and taking that path also
  brings single inheritance's constraint.

#### Next Step

In this lesson, both abstraction tools worked at the **instance** level: an abstract class's
body and an interface's default method were both called through an object, and which body
would run was always asked together with an instance's runtime type. The next lesson looks at
members belonging to the class itself, and measures how the decision's side flips there.

### Static Members and Initialization Blocks

Source: https://academia.sh/en/courses/java-object-model/static-members-and-initialization-blocks

A static method is not overridden, it is hidden: the same call spelling is picked from the declared type, and a static call made through a null reference even works. The class-level state's single copy, which access triggers class initialization, and the case where the same line goes to two separate sides are measured.

The previous lesson set two abstraction tools side by side: the abstract class worked with
single inheritance, the interface with multiple, and when two independent interfaces gave the
same default, the compiler refused to decide. What the two tools shared was that both worked
at the **instance** level. Every method called belonged to an object, so the decision mostly
came from the runtime type.

This lesson's subject is members belonging to the class itself: **the static method**, the
**class variable**, and the **initialization block**. Behind a method called without an
object, there is no runtime type; what remains is only the name the compiler sees. There are
three questions: does the same line go to different sides for an instance method versus a
static method, how many copies does class-level state carry, and which access triggers a
class's initialization block?

#### The Measurement Core

The course's core is the same pair: declared type `Base`, runtime type `Sub`. This lesson
adds two members to the pair — a class variable (`produced`) and an instance field (`turn`) —
but the existing members' behavior does not change.

- **CI21** — The two types are deliberately separated; the oracle is the rig itself. When
  `Base d = new Sub()` is written, which type an answer comes from is known before looking at
  the answer.
- **CI22** — Only members are added to the `Base`/`Sub` pair; the values the shared
  definition's members produce do not change.
- **CI23** — In the initialization measurement, every scenario runs in a **separate loader**;
  one scenario's initializing a class does not affect another.
- **CI24** — Initialization steps print their name when they run; order is a dump, not an
  inference.
- **CI25** — The only thing counted is which side the decision comes from. Instruction count
  and added steps are not counted in this course; the class file is read only to show where
  the decision froze, and the reading calls the compiler from within the program and parses
  the file it produces with the standard library.

```java
// Base.java — the course's core: instance method next to static method
class Base {
    static int produced = 0;
    final int turn;
    Base() { turn = ++produced; }
    String name() { return "base method"; }
    static String status() { return "base static"; }
}
```

```java
// Sub.java — subclass redeclaring the same two names
class Sub extends Base {
    @Override String name() { return "sub method"; }
    static String status() { return "sub static"; }
}
```

`name` carries an `@Override` mark, and the compiler accepts it: this is an **overriding**.
`status` carries no such mark — had it, compilation would stop. A static method cannot be
overridden; what happens when it is redeclared under the same name is called **hiding**, and
the behavior we saw hiding produce in fields repeats here for methods.

#### Same Spelling, Two Sides

The first measurement puts five calls side by side. Three are made through a reference
declared `Base`, one through a reference declared `Sub`, and the last through a reference
bound to no object at all.

```java
// Side.java — which type does the same call spelling get picked from
public class Side {
    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        Base d = new Sub();
        Sub a = new Sub();
        Base empty = null;
        System.out.printf("%-30s %-12s %s%n", "call written", "result", "decided by");
        System.out.printf("%-30s %-12s %s%n", "d.name()     [declared Base]", d.name(), "runtime type");
        System.out.printf("%-30s %-12s %s%n", "d.status()   [declared Base]", d.status(), "declared type");
        System.out.printf("%-30s %-12s %s%n", "a.name()     [declared Sub]", a.name(), "runtime type");
        System.out.printf("%-30s %-12s %s%n", "a.status()   [declared Sub]", a.status(), "declared type");
        System.out.printf("%-30s %-12s %s%n", "empty.status() [null ref]", empty.status(), "declared type");
        System.out.println();
        System.out.println("instance field -> d.turn " + d.turn + " | a.turn " + a.turn);
        System.out.println("class field -> Base.produced " + Base.produced + " | Sub.produced " + Sub.produced);
        Sub.produced = 99;
        System.out.println("after Sub.produced = 99, Base.produced: " + Base.produced);
    }
}
```

```
call written                   result       decided by
d.name()     [declared Base]   sub method   runtime type
d.status()   [declared Base]   base static  declared type
a.name()     [declared Sub]    sub method   runtime type
a.status()   [declared Sub]    sub static   declared type
empty.status() [null ref]      base static  declared type

instance field -> d.turn 1 | a.turn 2
class field -> Base.produced 2 | Sub.produced 2
after Sub.produced = 99, Base.produced: 99
```

The first two lines are written through the same `d` reference and give opposite answers.
`d.name()` returns **sub method**: the object is a `Sub`, and the method was picked from
there. `d.status()` returns **base static**: the object is still the same object, but the
pick was made from `d`'s declared type. The only difference between the two is that one of
the two declarations inside `Sub` starts with the word `static`.

The fifth line is proof of this reading. `empty` is bound to no object at all; even so, the
call `empty.status()` gives a result and raises no error. The reference is not even **read**.
A reference appearing in a static call's spelling is only a syntactic convenience; the
compiler takes that reference's **type**, and discards its value.

The bottom three lines answer the second question. Two objects were produced, and each
carries its own `turn` value: **1** and **2**. `produced`, in turn, gives the same number —
**2** — no matter which of the two names it is read through. The name written as
`Sub.produced` is not a separate counter; once `Sub.produced = 99` is written, `Base.produced`
also becomes 99. An instance field carries one copy per object; a class variable is **one**
single copy across the whole inheritance chain.

#### The Proof in the Class File

Behavior has been measured; now where the decision froze can be looked at. The measurement
core compiles a source text, reads the class file it produces, and returns two things for
every call inside a method's body: the call's **owner** as written in the class file, and the
call's **kind**.

```java
// Gauge.java — a call's owner and kind as written in the class file
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    record Call(String owner, String name, String kind) {}

    static Path compile(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Path f = d.resolve(className + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), f.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return d;
    }

    static List<Call> calls(String source, String className, String method) throws Exception {
        ClassModel cm = ClassFile.of().parse(compile(source, className).resolve(className + ".class"));
        List<Call> calls = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals(method) || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get())
                if (e instanceof InvokeInstruction iv)
                    calls.add(new Call(shortName(iv.owner().asInternalName()),
                            iv.name().stringValue(), kind(iv.opcode())));
        }
        return calls;
    }

    static String kind(Opcode o) {
        return switch (o) {
            case INVOKESTATIC -> "static call";
            case INVOKEVIRTUAL -> "virtual call";
            default -> "direct call";
        };
    }

    static String shortName(String internal) { return internal.substring(internal.lastIndexOf('/') + 1); }
}
```

```java
// Owner.java — what is the call's owner written as in the class file
public class Owner {
    static final String SOURCE = """
        class Base {
            String name() { return "base method"; }
            static String status() { return "base static"; }
        }
        class Sub extends Base {
            @Override String name() { return "sub method"; }
            static String status() { return "sub static"; }
        }
        class Caller {
            static String baseInstance(Base d) { return d.name(); }
            static String subInstance(Sub a) { return a.name(); }
            @SuppressWarnings("static-access")
            static String baseStatic(Base d) { return d.status(); }
            @SuppressWarnings("static-access")
            static String subStatic(Sub a) { return a.status(); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-13s %-24s %s%n", "method", "call in class file", "call kind");
        for (String y : new String[] {"baseInstance", "subInstance", "baseStatic", "subStatic"})
            for (Gauge.Call c : Gauge.calls(SOURCE, "Caller", y))
                System.out.printf("%-13s %-24s %s%n", y, c.owner() + "." + c.name(), c.kind());
    }
}
```

```
method        call in class file       call kind
baseInstance  Base.name                virtual call
subInstance   Sub.name                 virtual call
baseStatic    Base.status              static call
subStatic     Sub.status               static call
```

In all four lines, the owner written is the **declared type**; this is the table's most
misread spot. The `baseInstance` method also has `Base` as its owner, and yet the method that
actually runs is `Sub`'s. The distinction is not in the owner, it is in the **kind**. In a
virtual call, the owner is only a starting point: the virtual machine re-resolves the call
starting from the object's real class, so the `Base.name` text written in the class file does
not decide the result. In a static call, there is no resolving; the owner **is** the result
itself, and `Sub.status` and `Base.status` are **two separate targets**.

This reduces the lesson's main measurement to one sentence: a static call's target is written
at compile time and has no place left to be changed at runtime.

#### Which Access Triggers Initialization

Class-level state has to be set up somewhere, and that place is the **initialization block**.
When this block runs — not at program launch, but the first time the class is really needed,
and only once — was measured in the Java Fundamentals course and is not repeated here. The
question here is different: **which spelling** counts as being needed?

The measurement below runs four separate accesses in four separate loaders, so every scenario
starts from a clean slate. Initialization blocks print their name when they run.

```java
// Trigger.java — which access triggers class initialization
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

public class Trigger {
    static final String SOURCE = """
        class Base {
            static { System.out.println("  Base initialized"); }
            static int counter = 0;
            static final int CEILING = 100;
        }
        class Sub extends Base {
            static { System.out.println("  Sub initialized"); }
            static int subCounter = 0;
        }
        class S1 { static void run() { System.out.println("  value read: " + Sub.counter); } }
        class S2 { static void run() { System.out.println("  value read: " + Base.CEILING); } }
        class S3 { static void run() { System.out.println("  value read: " + Sub.subCounter); } }
        class S4 { static void run() { new Sub(); System.out.println("  a Sub was produced"); } }
        """;

    static void scenario(Path d, String driver, String spelling) throws Exception {
        System.out.println("access written: " + spelling);
        try (URLClassLoader cl = new URLClassLoader(new URL[] {d.toUri().toURL()}, null)) {
            var m = cl.loadClass(driver).getDeclaredMethod("run");
            m.setAccessible(true);
            m.invoke(null);
        }
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        Path d = Gauge.compile(SOURCE, "Base");
        scenario(d, "S1", "Sub.counter    [field declared in Base]");
        scenario(d, "S2", "Base.CEILING   [compile-time constant]");
        scenario(d, "S3", "Sub.subCounter [field declared in Sub]");
        scenario(d, "S4", "new Sub()");
    }
}
```

```
access written: Sub.counter    [field declared in Base]
  Base initialized
  value read: 0

access written: Base.CEILING   [compile-time constant]
  value read: 100

access written: Sub.subCounter [field declared in Sub]
  Base initialized
  Sub initialized
  value read: 0

access written: new Sub()
  Base initialized
  Sub initialized
  a Sub was produced
```

In the first scenario, the name written in the source is `Sub`, but the class initialized is
**`Base`**, and `Sub` is never initialized at all. The written name is not the trigger; the
class the field is **declared in** is the trigger, and that is found at compile time. The
third scenario uses the same spelling, but with a field declared inside `Sub`, and this time
both classes initialize, with the base class first. Same spelling, two separate outcomes; what
makes the difference is not the name itself, it is which class the name resolves to.

The second scenario goes further still: **no class initializes**, yet the value is read and
comes out correct. `CEILING` is a `static final` field whose value is a constant computable at
compile time; the compiler has written that value directly into the reading side, so `Base` is
never touched at runtime at all. Reading a class variable initializes the class; reading a
constant does not — and the two lines are indistinguishable in the source.

#### A Bounding Measurement: The Same Line Goes to Two Sides

The measurements up to now called an instance method and a static method on **separate
lines**. The real defect is born once the two sit side by side: a single line, a single
reference, two separate decisions.

```java
// SideBySide.java — the same line goes to two separate sides
public class SideBySide {
    @SuppressWarnings("static-access")
    static String line(Base d) { return d.name() + " / " + d.status(); }

    @SuppressWarnings("static-access")
    public static void main(String[] args) {
        for (Base d : new Base[] {new Base(), new Sub()})
            System.out.printf("Base d = new %-6s -> %s%n", d.getClass().getSimpleName() + "()", line(d));
        Sub a = new Sub();
        System.out.printf("Sub a = new %-6s -> %s%n", "Sub()", a.name() + " / " + a.status());
    }
}
```

```
Base d = new Base() -> base method / base static
Base d = new Sub()  -> sub method / base static
Sub a = new Sub()  -> sub method / sub static
```

The `line` method never changed; the same body ran on all three lines. Yet the three results
give three separate combinations. In the first line the two answers agree, because the
declared type and the runtime type are the same. They agree in the third line too, for the
same reason. **The split is visible only on the second line** — and exactly there, a
programmer's expected reading breaks: the object is a `Sub`, yet the status information comes
from `Base`.

Two boundaries follow from this. First, there is no situation where the rule reverses; a
static member is always picked from the declared type. Second, and more important, **the
split cannot be observed in most programs**: as long as the declared type and the runtime type
are not separated, the two sides give the same answer and the difference never surfaces at
all. The day a subclass is written for a class, a line that had worked correctly up to that
day can silently change direction. The defect's cost is not paid where it is written, it is
paid where the subclass is **used** — and what shows up there is not an error, it is a
**wrong value**.

#### Summary

- A static method is not overridden, it is **hidden**: `d.name()` gives **sub method** from
  the runtime type, `d.status()` gives **base static** from the declared type; the only
  difference between the two lines is the word `static` in the declaration.
- A static call never reads the reference's value at all; `empty.status()` made through a null
  reference gives a result and raises no error.
- An instance field carries one copy per object (`turn` gives 1 and 2), a class variable is
  single across the inheritance chain: once `Sub.produced = 99` is written, `Base.produced`
  becomes 99 too.
- In the class file, all four calls' owner is the declared type; the distinction is in the
  kind. In a virtual call the owner is a starting point, in a static call it is the result
  itself.
- Class initialization is triggered not by the written name, but by the class the field is
  **declared in**: `Sub.counter` initializes only `Base`, while `Base.CEILING` initializes no
  class at all.
- The split is visible only when the declared type and the runtime type are separated; in two
  of the three-line measurement, the two sides give the same answer and the difference cannot
  be observed.

#### Next Step

In this lesson's last measurement, a value was read without ever triggering class
initialization: `CEILING` was a `static final` field, and its value had passed to the reading
side. That single word, `final`, showed up here only as a side effect; in fact it does three
separate jobs at once, and in all three it carries the decision to **compile time**. The same
word blocks reassigning a local variable, overriding a method, and extending a class. The next
lesson measures these three constraints separately, shows the consequence of a constant value
being embedded in the reading class, and bounds, with the same measurement, that a `final`
reference does not freeze the object — meaning it is not immutability.

### The `final` Keyword

Source: https://academia.sh/en/courses/java-object-model/final-keyword

A single keyword places three separate restrictions and enforces all three at compile time: reassignment, overriding, and extension. A constant final field's value is written into the reading class, and the old value stays in place when the source changes; a final reference, in turn, does not freeze the object.

The previous lesson's last measurement read a value without ever initializing the class it
belonged to: `Base.CEILING` was a `static final` field, and the correct number arrived without
the initialization block ever running. The reason for that result sat in a single word,
`final`, and it showed up there only as a side effect.

This lesson measures that word directly. `final` can be written in three places — at the head
of a variable, a method, and a class — and it places three separate restrictions. What the
three share is that all three are decided **at compile time**: none of them is a condition
tested at runtime. There are three questions: how many of these restrictions stop at the
compiler, where does a constant field's value get written, and what does a `final` reference
protect?

#### The Measurement Core

The measurement does three things: it compiles a source and reports whether it compiled, reads
the `final` flags in the class file it produces, and runs the compiled classes in a separate
loader.

- **CI26** — In the compile trials, the oracle is the rig itself: a single line changes in
  every trial, so a compile stopping can be tied to only that line. The result is read through
  a single exit code; the error text is not read, because that text is locale-dependent.
- **CI27** — Every trial compiles in its own temporary directory; trials do not see each
  other's class files.
- **CI28** — In the embedding measurement, `Fixed` is compiled **twice**, `Reader` **once**;
  the class file read on the second run is the same file as the first.
- **CI29** — The run happens in a separate loader, so the class loaded on the first run does
  not carry over to the second.
- **CI30** — No environment-dependent data is read; what is counted is the number of trials
  that compile, the flags in the class file, and the value read.

```java
// Gauge.java — compile result, the final flag in the class file, and the reading side's trace
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.lang.reflect.AccessFlag;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    static Path newDir() throws Exception { return Files.createTempDirectory("gauge"); }

    static boolean compile(Path d, String name, String source) throws Exception {
        Path f = d.resolve(name + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), "-cp", d.toString(), f.toString()) == 0;
    }

    static String finalFlags(Path d, String className) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(className + ".class"));
        List<String> flags = new ArrayList<>();
        if (cm.flags().has(AccessFlag.FINAL)) flags.add("class " + className);
        for (FieldModel x : cm.fields())
            if (x.flags().has(AccessFlag.FINAL)) flags.add("field " + x.fieldName().stringValue());
        for (MethodModel x : cm.methods())
            if (x.flags().has(AccessFlag.FINAL)) flags.add("method " + x.methodName().stringValue());
        return String.join(", ", flags);
    }

    static List<String> traces(Path d, String className, String method) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(className + ".class"));
        List<String> trace = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (!m.methodName().stringValue().equals(method) || m.code().isEmpty()) continue;
            for (CodeElement e : m.code().get()) {
                if (e instanceof FieldInstruction f)
                    trace.add("field read " + shortName(f.owner().asInternalName())
                            + "." + f.name().stringValue());
                else if (e instanceof ConstantInstruction c)
                    trace.add("constant load " + c.constantValue());
                else if (e instanceof InvokeInstruction iv)
                    trace.add(switch (iv.opcode()) {
                        case INVOKEVIRTUAL -> "virtual call ";
                        case INVOKESTATIC -> "static call ";
                        default -> "direct call ";
                    } + shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
            }
        }
        return trace;
    }

    static Object run(Path d, String className, String method) throws Exception {
        try (URLClassLoader cl = new URLClassLoader(new URL[] {d.toUri().toURL()}, null)) {
            var m = cl.loadClass(className).getDeclaredMethod(method);
            m.setAccessible(true);
            return m.invoke(null);
        }
    }

    static String shortName(String internal) { return internal.substring(internal.lastIndexOf('/') + 1); }
}
```

#### Three Restrictions, All at Compile Time

The first measurement compiles nine sources. The trials are set up in pairs: next to every
`final` trial sits its twin, whose only difference is that word's absence.

```java
// Restriction.java — final places three separate restrictions, all at compile time
import java.nio.file.Path;

public class Restriction {
    static final String[][] TRIALS = {
        {"reassigning a local variable",  "class T { void f() { int y = 1; y = 2; } }"},
        {"reassigning a final local",     "class T { void f() { final int y = 1; y = 2; } }"},
        {"reassigning a field",           "class T { int a; T() { a = 1; } void f() { a = 2; } }"},
        {"reassigning a final field",     "class T { final int a; T() { a = 1; } void f() { a = 2; } }"},
        {"assigning a final field in constructor", "class T { final int a; T() { a = 1; } }"},
        {"overriding a method",           "class A { int f() { return 1; } } class T extends A { @Override int f() { return 2; } }"},
        {"overriding a final method",     "class A { final int f() { return 1; } } class T extends A { int f() { return 2; } }"},
        {"extending a class",             "class A { } class T extends A { }"},
        {"extending a final class",       "final class A { } class T extends A { }"},
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-42s %s%n", "spelling tried", "does it compile");
        for (String[] d : TRIALS)
            System.out.printf("%-42s %s%n", d[0], Gauge.compile(Gauge.newDir(), "T", d[1]));

        String source = """
            final class Flagged {
                final int fixedField = 1;
                int otherField = 2;
                final int locked() { return 1; }
                int free() { final int y = 1; return y; }
            }
            """;
        Path d = Gauge.newDir();
        Gauge.compile(d, "Flagged", source);
        System.out.println();
        System.out.println("carrying the final flag in the class file:");
        System.out.println("  " + Gauge.finalFlags(d, "Flagged"));

        Path e = Gauge.newDir();
        Gauge.compile(e, "A", """
            class A { final int locked() { return 1; } int free() { return 2; } }
            class Caller {
                static int a(A x) { return x.locked(); }
                static int b(A x) { return x.free(); }
            }
            """);
        System.out.println();
        System.out.printf("%-22s : %s%n", "final method call", Gauge.traces(e, "Caller", "a"));
        System.out.printf("%-22s : %s%n", "non-final method call", Gauge.traces(e, "Caller", "b"));
    }
}
```

```
spelling tried                             does it compile
reassigning a local variable               true
reassigning a final local                  false
reassigning a field                        true
reassigning a final field                  false
assigning a final field in constructor     true
overriding a method                        true
overriding a final method                  false
extending a class                          true
extending a final class                    false

carrying the final flag in the class file:
  class Flagged, field fixedField, method locked

final method call      : [virtual call A.locked]
non-final method call  : [virtual call A.free]
```

**Four** of nine trials stopped, and all four were a `final` trial. Their twins compiled
without a hitch; the only difference is that word. The fifth row shows the restriction's exact
place: assigning to a `final` field is not forbidden, **re**assigning it is. The field can be
assigned once, in the constructor, and if it is not assigned there, compilation stops just the
same; the restriction is not "never write to it," it is "write to it exactly once."

The seventh row ties into the course's question. Once a method is declared `final`, no other
answer can be written for it from a subclass. In the previous lesson, a call had **two**
possible answers, and the runtime type decided which one arrived. `final` brings that number
down to **one**: whatever the runtime type is, a single body remains, and the question loses
its meaning. `final` does not move the runtime type's decision to the declared type; it
**removes** the decision.

The output's last two lines sharpen this reading one step further. A `final` method's call and
a non-final one's are both written to the class file as a **virtual call**; there is no
difference between the two lines at all. So the compiler does not change the call's mechanism,
it reduces the set of bodies the call could select to one. This is the difference from the
previous lesson's static call: there, the writing stayed unchanged but the **target**
changed; here, target selection stays in place, but only a single candidate remains to be
selected.

The middle block separates where the restrictions stop. Three things carry the `final` flag
in the class file: the class itself, `fixedField`, and the `locked` method. The `final int y`
declaration inside `free`'s body is **not** on the list. A local variable's restriction is
never written to the class file at all; it lives only in the compiler's memory and disappears
once compilation ends. The other two stay as flags, because **another compile** will read
them: a compiler writing a subclass has to be able to see `locked`'s flag.

#### Where Is the Constant Value Written

The previous lesson's `CEILING` measurement left a question open: given the class was never
initialized, where did the value come from? The measurement below puts two fields side by
side. `CEILING` takes its value directly from a number; `FLOOR` takes the same number from a
method call. Both are `static final`.

```java
// Embed.java — a constant final field's value is written into the reading class
import java.nio.file.Path;

public class Embed {
    static String constant(int n) {
        return """
            class Fixed {
                static final int CEILING = %d;
                static final int FLOOR = compute();
                static int compute() { return %d; }
            }
            """.formatted(n, n);
    }

    static final String READER = """
        class Reader {
            static int ceiling() { return Fixed.CEILING; }
            static int floor() { return Fixed.FLOOR; }
            static String value() { return "CEILING=" + ceiling() + " FLOOR=" + floor(); }
        }
        """;

    public static void main(String[] args) throws Exception {
        Path d = Gauge.newDir();
        Gauge.compile(d, "Fixed", constant(100));
        Gauge.compile(d, "Reader", READER);
        for (String y : new String[] {"ceiling", "floor"})
            System.out.println("trace inside Reader." + y + "(): " + Gauge.traces(d, "Reader", y));
        System.out.println();
        System.out.println("first run    : " + Gauge.run(d, "Reader", "value"));
        Gauge.compile(d, "Fixed", constant(200));
        System.out.println("Fixed recompiled with 200, Reader not recompiled");
        System.out.println("second run   : " + Gauge.run(d, "Reader", "value"));
    }
}
```

```
trace inside Reader.ceiling(): [constant load 100]
trace inside Reader.floor(): [field read Fixed.FLOOR]

first run    : CEILING=100 FLOOR=100
Fixed recompiled with 200, Reader not recompiled
second run   : CEILING=100 FLOOR=200
```

The first two lines answer the question. `ceiling`'s class file carries no reference to the
`Fixed` class at all; there is only **the number 100 itself**. `floor`'s method, in turn,
carries a field read, and that read's owner is `Fixed`. In the source the two lines are
identical — both write a field name after a dot — but one has **copied** the value into the
reading class, the other has **left a link**.

The cost of copying shows on the second run. `Fixed` was recompiled with the value 200;
`Reader` was not recompiled and sits on disk in its old form. When run, `FLOOR` gives the new
value, **200**: the link stayed in place, and was read through it. `CEILING`, though, still
says **100**. Someone looking at the source will see 200 inside `Fixed` and read 100 in the
running program.

This is the sharpest form of the question this course asks. The decision here is split not
between declared type and runtime type, but between **compile time and runtime** — yet the
result is the same: a programmer who does not know which side is answering assumes the value
they see in the source is the one running. The defect is not in whoever changed `Fixed`
either; it is in `Reader` not being recompiled, and it produces no error message anywhere.

#### A Bounding Measurement: A `final` Reference Does Not Freeze the Object

All three restrictions have been measured, and all three stop at compile time. Now the
claim's boundary: what happens to the **inside** of an object bound to a `final` name?

```java
// Freeze.java — a final reference does not freeze the object
import java.util.Arrays;

public class Freeze {
    static class Entry {
        String name;
        int count;
        Entry(String name, int count) { this.name = name; this.count = count; }
        @Override public String toString() { return name + ":" + count; }
    }

    static final String BODY = """
        class Trial {
            static class Entry { int count; }
            void f() { final Entry k = new Entry(); %s }
        }
        """;

    public static void main(String[] args) throws Exception {
        final Entry k = new Entry("bolt", 12);
        final int[] weight = {10, 20, 30};
        Entry first = k;
        System.out.println("start            : " + k + " " + Arrays.toString(weight));
        k.name = "nut";
        k.count = 99;
        weight[0] = 99;
        System.out.println("after change     : " + k + " " + Arrays.toString(weight));
        System.out.println("reference same   : " + (k == first));
        System.out.println();
        for (String[] y : new String[][] {{"k.count = 1;", "changing a field"},
                                          {"k = new Entry();", "rebinding the reference"}})
            System.out.printf("%-26s -> does it compile: %s%n", y[1],
                    Gauge.compile(Gauge.newDir(), "Trial", BODY.formatted(y[0])));
    }
}
```

```
start            : bolt:12 [10, 20, 30]
after change     : nut:99 [99, 20, 30]
reference same   : true

changing a field           -> does it compile: true
rebinding the reference    -> does it compile: false
```

`k` is a `final` local variable, and both of its fields changed; `weight` is `final` too, and
its first element changed. Even so, `k == first` is still **true**: the reference never
changed. What is protected is not the object, it is **the link between the name and the
object**.

The last two lines reduce the distinction to a single measurement. On the same `final`
declaration, the line changing a field compiles, the line rebinding the reference does not.
`final` places a restriction on top of the assignment model measured in the Java Fundamentals
course, and that restriction is only worth something at **binding**: when the value itself is
a reference, the object that reference points to sits outside this restriction.

Two consequences follow from this. First, a `final` field is not **immutability**; for a type
to be immutable, its fields being `final` is not enough — the objects those fields point to
also have to not change. Second, there is a case where the measurement cannot be observed: had
a number or a string been used instead of `weight`, the distinction would never have shown at
all, because those values have no changeable inside. Whether the restriction is enough depends
not on the word `final`, but on the **type**.

#### Summary

- `final` places three separate restrictions, and four of nine trials stop for exactly this
  word: reassignment, overriding, and extension.
- The restriction is not "never assign," it is "assign exactly once"; a `final` field can be
  assigned in the constructor.
- A `final` method reduces the number of answers the runtime type could give from two to
  **one**; it does not move the decision to the other side, it removes it.
- The class file carries the `final` flag on the class, the field, and the method; a local
  variable's restriction is never written, because no other compile needs to read it.
- A constant `static final` field's value is written into the reading class: a reader not
  recompiled after the source becomes 200 keeps reading **100**, while a computed field gives
  **200**.
- A `final` reference does not freeze the object; its fields and an array's elements change,
  only the link between the name and the object is preserved.

#### Next Step

In this lesson's measurement, one restriction was never written into the class file at all: a
`final` local variable's restriction stopped only at the compiler and vanished once
compilation ended. So what is that restriction for? There is a place in Java where the
compiler wants this very promise **on its own, without it being written**: a class defined
inside a method body, when it uses a local variable from outside, requires that variable never
be assigned again. The reason for the requirement is that the variable gets copied in there —
and carrying that copy produces new class files never written in the source. The next lesson
counts those files: how many separate files does a class written nested inside another
produce, which of them carry a hidden field pointing to the enclosing instance, and when does
the difference between that hidden field and the copied value become visible?

### Inner and Local Classes

Source: https://academia.sh/en/courses/java-object-model/inner-and-local-classes

A single source file produces six class files, and four of those carry hidden fields never written in the source. An inner class binds to the enclosing instance through a field and reads that field live; a local class copies the variable it captures through a constructor parameter, which is why that variable can never be reassigned.

The previous lesson's measurement left a restriction vanishing without ever being written into
a class file: a `final` local variable's restriction stopped only at the compiler. If that word
disappears once compilation ends, what is it for? There is a place in Java where the compiler
wants that very same word **on its own, without it being written** — and there, its
counterpart becomes visible.

That place is where a class is written inside another class. In Java, a class can be defined
in another class's body, in a method's body, or directly inside a `new` expression. In the
source, all of these sit nested, in a single file; a class file, though, cannot be nested.
There are three questions: how many separate files does this nesting open into, how do the
produced files bind to the outside, and which side does that binding form leave the decision
to?

#### The Measurement Core

The measurement compiles a source and reads every class file produced. For each file, two
things are counted: its fields — those written in the source together with the **hidden** ones
the compiler adds — and the constructor's signature.

- **CI31** — The oracle is the source itself: we know which field we wrote, so any extra field
  appearing in the list is one the compiler added. A hidden field is told apart not by guessing
  but by its flag in the class file.
- **CI32** — All class files are produced from a **single** source file; the count covers every
  file in that directory.
- **CI33** — In the capture measurement, the object is produced **before** the outer field's
  value is changed, so a later reading of that value is proof of a live read.
- **CI34** — In the compile trials, a single line changes, and every trial compiles in its own
  temporary directory.
- **CI35** — No environment-dependent data is read; what is counted is file count, field count,
  and constructor parameters.

```java
// Gauge.java — the class files produced, the synthetic fields they carry, and constructor signatures
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    static Path newDir() throws Exception { return Files.createTempDirectory("gauge"); }

    static boolean compile(Path d, String name, String source) throws Exception {
        Path f = d.resolve(name + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), "-cp", d.toString(), f.toString()) == 0;
    }

    static List<String> classFiles(Path d) throws Exception {
        try (var s = Files.list(d)) {
            return s.map(p -> p.getFileName().toString())
                    .filter(n -> n.endsWith(".class")).sorted().toList();
        }
    }

    static String fields(Path d, String file) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(file));
        List<String> a = new ArrayList<>();
        for (FieldModel f : cm.fields())
            a.add(f.fieldName().stringValue()
                    + (f.flags().has(AccessFlag.SYNTHETIC) ? " [synthetic]" : ""));
        return a.isEmpty() ? "-" : String.join(", ", a);
    }

    static String constructor(Path d, String file) throws Exception {
        ClassModel cm = ClassFile.of().parse(d.resolve(file));
        for (MethodModel m : cm.methods())
            if (m.methodName().stringValue().equals("<init>")) {
                List<String> p = new ArrayList<>();
                for (var a : m.methodTypeSymbol().parameterList()) p.add(a.displayName());
                return "(" + String.join(", ", p) + ")";
            }
        return "-";
    }
}
```

#### One Source File, Six Class Files

The measured source holds a single outer class and places five separate forms inside it: an
inner class, a static nested class, a local class defined in an instance method, a local class
defined in a static method, and an anonymous class.

```java
// Production.java — how many files does nested class writing produce
import java.nio.file.Path;

public class Production {
    static final String SOURCE = """
        class Outer {
            int outerField = 1;
            class Inner { int read() { return outerField; } }
            static class Nest { int read() { return 2; } }
            Runnable instanceLocal() {
                int y = 3;
                class Local implements Runnable { public void run() { use(outerField + y); } }
                return new Local();
            }
            static Runnable staticLocal() {
                int y = 4;
                class SLocal implements Runnable { public void run() { use(y); } }
                return new SLocal();
            }
            Runnable anonymous() {
                int y = 5;
                return new Runnable() { public void run() { use(outerField + y); } };
            }
            static void use(int n) { }
        }
        """;

    public static void main(String[] args) throws Exception {
        Path d = Gauge.newDir();
        Gauge.compile(d, "Outer", SOURCE);
        System.out.printf("%-20s %-40s %s%n", "class file", "fields", "constructor sig");
        for (String f : Gauge.classFiles(d))
            System.out.printf("%-20s %-40s %s%n", f, Gauge.fields(d, f), Gauge.constructor(d, f));
        System.out.println("files produced: " + Gauge.classFiles(d).size());
    }
}
```

```
class file           fields                                   constructor sig
Outer$1.class        val$y [synthetic], this$0 [synthetic]    (Outer, int)
Outer$1Local.class   val$y [synthetic], this$0 [synthetic]    (Outer, int)
Outer$1SLocal.class  val$y [synthetic]                        (int)
Outer$Inner.class    this$0 [synthetic]                       (Outer)
Outer$Nest.class     -                                        ()
Outer.class          outerField                               ()
files produced: 6
```

A single source file gave off **six** class files. Nesting does not exist at the class-file
level; every form gets its own file, and the names are derived from the outer class's name. The
anonymous class had no name at all in the source, and it still has a file.

The second column carries the real measurement. The only field written in the source is
`outerField`, and it appears only in `Outer`'s file. The four fields showing up in the other
files are **not written in the source**: `this$0` and `val$y`. Four of the five forms carry at
least one hidden field; the only form that does not is the static nested class.

The third column shows how the hidden fields get filled. `Outer$Inner`'s constructor takes an
`Outer` parameter; no constructor was ever written for this class in the source, and whoever
writes `new` supplies no parameter either. `Outer$1Local` and `Outer$1` — the local class
defined in an instance method, and the anonymous class — take two parameters: the enclosing
instance and the captured number. `Outer$1SLocal` takes only the number, because there is no
enclosing instance in a static method. The compiler has added both the field and the parameter
filling it; writing something nested is not a matter of appearance, it is **a binding
mechanism**.

The table splits the five forms into three groups by this mechanism. Those carrying both the
enclosing instance and the captured value: the local class defined in an instance method, and
the anonymous class. Those carrying only one: the inner class carrying the enclosing instance,
and the local class in a static method carrying only the captured value. Those carrying
neither: the static nested class. In the source, these five forms are written in a very
similar way — all are a class declaration, and the difference between them is a word or a
position — but they turn into three separate structures in the class file. Choosing a form is
not choosing a spelling convenience.

#### Shared Field, Copied Local

The hidden fields carry two separate things, and the two do not behave the same way. `this$0`
is a **reference**; `val$y` is a **copy of a value**. To see the difference, both are read
through the same object.

```java
// Capture.java — the outer field is shared, the captured local is copied
public class Capture {
    int field = 1;

    Runnable produce() {
        int local = 1;
        class Trace implements Runnable {
            @Override public void run() { System.out.println("  field=" + field + " local=" + local); }
        }
        Runnable r = new Trace();
        field = 2;
        return r;
    }

    static final String BODY = """
        class Trial {
            int field = 1;
            Runnable f() {
                int local = 1;
                class Trace implements Runnable { public void run() { use(field + local); } }
                Runnable r = new Trace();
                %s
                return r;
            }
            static void use(int n) { }
        }
        """;

    public static void main(String[] args) throws Exception {
        Capture y = new Capture();
        Runnable r = y.produce();
        System.out.println("after the object was produced, field set to 2, local gone");
        r.run();
        y.field = 3;
        System.out.println("field written to 3 from outside");
        r.run();
        System.out.println();
        for (String[] s : new String[][] {{"field = 2;", "reassigning the outer field"},
                                          {"local = 2;", "reassigning the captured local"}})
            System.out.printf("%-32s -> does it compile: %s%n", s[1],
                    Gauge.compile(Gauge.newDir(), "Trial", BODY.formatted(s[0])));
    }
}
```

```
after the object was produced, field set to 2, local gone
  field=2 local=1
field written to 3 from outside
  field=3 local=1

reassigning the outer field      -> does it compile: true
reassigning the captured local   -> does it compile: false
```

At the moment the `Trace` object was produced, `field` was 1 and `local` was 1. **After** the
object was produced, `field` changed twice, and both changes showed up in the value read: first
2, then 3. `local`, in turn, stayed **1** on both reads. Both names are written in the source
the same way, with no qualifier at all — and yet one tracks the outside value, the other reads
a copy frozen at the moment of production.

The difference comes from what the hidden fields carry. `this$0` points to the outer object,
and the field is fetched from that object on every read; so a change from outside is visible.
`val$y` holds the number itself, filled in once, in the constructor; no link to the outside
variable remains at all. A method's local variable disappears once the method ends anyway — the
object, though, keeps living, so there is nothing left to track.

The last two lines show how the compiler protects this copy. The version reassigning the outer
field compiles; the version reassigning the captured local **does not compile**. The word
`final` is not written anywhere in the source; the compiler places the restriction on its own,
because the only case where the copy could disagree with the original is the original
changing. The previous lesson's measurement — "`final` on a local is never written to the class
file" — finds its counterpart here: that restriction leaves no flag behind, but it protects a
**copy**.

#### Which Outer Instance — A Third Source

The decision's side splits in two in this section. What the captured value will be is settled
**the moment the constructor is called**, and never asked again. Which outer object gets read,
though, is asked **again on every read** — and the answer comes from neither the declared type
nor the runtime type.

```java
// Binding.java — which outer instance it is bound to is decided at runtime
public class Binding {
    static class Outer {
        String name;
        Outer(String name) { this.name = name; }
        class Inner { String read() { return name; } }
    }

    public static void main(String[] args) throws Exception {
        Outer a = new Outer("first");
        Outer b = new Outer("second");
        Outer.Inner x = a.new Inner();
        Outer.Inner y = b.new Inner();
        System.out.println("are x and y the same class: " + (x.getClass() == y.getClass()));
        System.out.println("x.read()              : " + x.read());
        System.out.println("y.read()              : " + y.read());
        System.out.println();
        for (String[] s : new String[][] {
                {"I make() { return new D().new I(); }", "with an outer instance"},
                {"static I make() { return new I(); }",  "without an outer instance"}})
            System.out.printf("%-26s -> does it compile: %s%n", s[1],
                    Gauge.compile(Gauge.newDir(), "D",
                            "class D { class I { } " + s[0] + " }"));
    }
}
```

```
are x and y the same class: true
x.read()              : first
y.read()              : second

with an outer instance     -> does it compile: true
without an outer instance  -> does it compile: false
```

`x` and `y` are the same class — the output's first line confirms this — and their declared
types are the same too. Yet the same method call gives two separate results. Neither of the two
sources this course has measured so far can answer this: the two objects' runtime type is one,
and their declared type is one. What decides the result is **the hidden field's value** — which
outer object got written into `this$0`.

This does not add a third answer to the course's question; it sharpens it. Where an overridden
method has the object's class give the answer, here it is **a field of the object** that gives
it; both belong to runtime, but one is bound to type and the other to data. The practical
consequence of this difference is: in a program with no subclass ever added, the first source
never changes, while the second is settled fresh on every single `new`.

The bottom two lines show this field's necessity. An inner class can only be produced through
an outer instance; a `new` written from a context with no outer instance stops compilation. In a
place where the hidden field cannot be filled, the object cannot even be constructed.

#### A Bounding Measurement: Where the Hidden Field Is Absent

The claim's boundary sits in two rows of the first table. `Outer$Nest` carries no field at all,
and its constructor takes no parameter; `Outer$1SLocal`, in turn, carries no `this$0`, only
`val$y`.

A static nested class, despite being written in the outer class's body, forms no bond with an
outer **instance** at all. The nesting in the source is only for namespace and visibility; the
class produced is an independent class sitting at the same level as the outer class. This is
why the question "which outer object" has no runtime answer here — the question is never asked
at all. The same reasoning applies to a local class defined in a static method: since there is
no outer instance while that method is running, there is no reference to carry either, but the
captured local variable still gets copied there too.

Two consequences follow from this. First, what decides whether a hidden field exists is not
where the class is written, it is **what it accesses from outside**: a form that never touches
the enclosing instance carries no reference. Second, the measurement can become unobservable.
When an inner class is written that never reads the outer field, `this$0` still gets added, but
no trace of it shows in behavior; the difference between the two forms only surfaces once the
outer object keeps living and can no longer be released for exactly this reason. A programmer
unaware that a link was formed only notices, indirectly, that the object they thought was
discarded is still being held.

#### Summary

- A single source file produces **six** class files; nesting does not exist at the class-file
  level, even the anonymous class has its own file, and four of the five forms carry a hidden
  field never written in the source: `this$0` holding the enclosing instance, `val$y` holding
  the captured value.
- Two inner-class instances of the same class and the same declared type give two separate
  answers to the same call; what gives the answer is not the type, it is the enclosing instance
  written into the `this$0` field.
- What fills the hidden fields is the constructor's hidden parameters: the inner class's
  constructor carries the signature `(Outer)`, the local class in an instance method
  `(Outer, int)`, the one in a static method `(int)`.
- The outer field is **shared** and read live — after the object was produced, the values 2 and
  3 were both read; the captured local is **copied** and stayed 1 on both reads.
- Even with no `final` written in the source, reassigning the captured variable stops
  compilation; the restriction keeps the copy from disagreeing with the original.
- A static nested class carries no hidden field, and a local class in a static method carries
  no `this$0`: what decides whether a hidden field exists is not where it is written, it is
  what it accesses from outside.

#### Next Step

In this lesson, the compiler produced **classes** never written in the source, and gave them
fields and constructor parameters never written in the source either. What everything produced
shared was taking on a binding job a programmer would otherwise have had to write by hand.
There is a far more visible use of that same production ability: starting from a one-line
declaration, letting the compiler produce a data carrier's entire surface — its field readers,
its equality, its hash value, and its string conversion. The next lesson counts these members
and asks which side each one's decision is left to; it then measures how far the produced
equality protects, meaning how a mutable part inside a data carrier can be changed from
outside.

### Records and Enumerations

Source: https://academia.sh/en/courses/java-object-model/records-and-enumerations

A one-line record declaration produces ten members in the class file, and the produced class cannot be extended, meaning its declared type and runtime type can never come apart. In an enum, a constant with its own body written takes its own class; a record's produced equality, in turn, protects only shallowly.

The previous lesson had the compiler produce classes never written in the source, and give
them hidden fields and hidden constructor parameters. What everything produced shared was
taking on a binding job a programmer would otherwise write by hand — but what was produced
stayed invisible: none of it was a surface callable from the source.

This lesson puts that same production ability to a visible job. In Java, a data carrier's
entire surface — its field readers, equality, hash value, and string conversion — can be
produced from a single line; that line's name is the **record** declaration. An
**enumeration**, defining a fixed set, does a similar production. There are three questions:
how many members does one line produce, which side do the produced members leave the decision
to, and how far does the produced equality protect?

#### The Measurement Core

The measurement compiles a source, reads the field and method names in the class file it
produces, reports whether the class can be extended, and also returns whether a given source
compiles at all.

- **CI36** — The oracle is the source itself: the record declaration's body is empty, so every
  member appearing in the class file is one the compiler produced.
- **CI37** — The member list is read from the class file; the count is based on the file
  produced, not the source's spelling.
- **CI38** — Compile trials run in their own temporary directories, and the result is read
  through a single exit code.
- **CI39** — In the equality measurement, two objects are constructed separately; identity is
  compared only with `==`, and no identity number is printed.
- **CI40** — Hash value is invoked only with the question of whether two objects' hashes are
  **equal to each other**, and that question is asked only for components producing a
  value-based hash.

```java
// Gauge.java — members in the class file and the extension trial
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    record ClassInfo(boolean isFinal, List<String> fields, List<String> methods) {}

    static Path newDir() throws Exception { return Files.createTempDirectory("gauge"); }

    static boolean compile(Path d, String name, String source) throws Exception {
        Path f = d.resolve(name + ".java");
        Files.writeString(f, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        return ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), "-cp", d.toString(), f.toString()) == 0;
    }

    static ClassInfo read(String source, String name) throws Exception {
        Path d = newDir();
        if (!compile(d, name, source)) throw new IllegalStateException("did not compile: " + name);
        ClassModel cm = ClassFile.of().parse(d.resolve(name + ".class"));
        List<String> fields = new ArrayList<>();
        for (FieldModel f : cm.fields())
            fields.add(f.fieldName().stringValue()
                    + (f.flags().has(AccessFlag.FINAL) ? " [final]" : ""));
        List<String> methods = new ArrayList<>();
        for (MethodModel m : cm.methods()) methods.add(m.methodName().stringValue());
        return new ClassInfo(cm.flags().has(AccessFlag.FINAL), fields, methods);
    }
}
```

#### One Line, Ten Members

The measured source is a single line, and its body is empty. The course's concrete axis
continues here too: a three-field inventory record — name, count, and weight in grams.

```java
// Produced.java — how many members come out of a one-line record declaration
import java.util.TreeSet;

public class Produced {
    static final String MANUAL = """
        class Manual {
            private final String name; private final int count; private final long weight;
            Manual(String name, int count, long weight) {
                this.name = name; this.count = count; this.weight = weight;
            }
            public String name() { return name; }
            public int count() { return count; }
            public long weight() { return weight; }
            @Override public boolean equals(Object o) {
                return o instanceof Manual e
                        && name.equals(e.name) && count == e.count && weight == e.weight;
            }
            @Override public int hashCode() {
                return java.util.Objects.hash(name, count, weight);
            }
            @Override public String toString() {
                return "Manual[name=" + name + ", count=" + count + ", weight=" + weight + "]";
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        String source = "record Entry(String name, int count, long weight) { }";
        Gauge.ClassInfo k = Gauge.read(source, "Entry");
        System.out.println("source: " + source);
        System.out.println("fields (" + k.fields().size() + "): " + k.fields());
        System.out.println("methods (" + k.methods().size() + "): " + k.methods());
        System.out.println("class final: " + k.isFinal());
        System.out.println();
        Gauge.ClassInfo e = Gauge.read(MANUAL, "Manual");
        System.out.println("source lines: record 1, hand-written " + MANUAL.strip().lines().count());
        System.out.println("member count : record " + (k.fields().size() + k.methods().size())
                + ", hand-written " + (e.fields().size() + e.methods().size()));
        System.out.println("method names equal: "
                + new TreeSet<>(k.methods()).equals(new TreeSet<>(e.methods())));
        System.out.println();
        System.out.printf("%-34s -> does it compile: %s%n", "extending the record",
                Gauge.compile(Gauge.newDir(), "T", source + " class T extends Entry { }"));
        System.out.printf("%-34s -> does it compile: %s%n", "record implementing an interface",
                Gauge.compile(Gauge.newDir(), "T", "interface T { }"
                        + " record Entry(String name) implements T { }"));
        System.out.printf("%-34s -> does it compile: %s%n", "reassigning its field",
                Gauge.compile(Gauge.newDir(), "T",
                        "record Entry(String name) { } class T { void f(Entry k) { k.name = \"x\"; } }"));
    }
}
```

```
source: record Entry(String name, int count, long weight) { }
fields (3): [name [final], count [final], weight [final]]
methods (7): [<init>, toString, hashCode, equals, name, count, weight]
class final: true

source lines: record 1, hand-written 19
member count : record 10, hand-written 10
method names equal: true

extending the record               -> does it compile: false
record implementing an interface   -> does it compile: true
reassigning its field              -> does it compile: false
```

A single declaration line was written in the source; the class file carries **three fields**
and **seven methods**, **ten members** in total. All three fields carry the `final` flag. Of
the seven methods, three are **component readers** (`name`, `count`, `weight`), three are the
surface Java expects from every object (`toString`, `hashCode`, `equals`), and one is the
**canonical constructor** taking the components. This is where it differs from the previous
lesson's hidden fields: these are not hidden, they are a callable surface.

The middle three lines measure what this surface costs when written by hand. A class building
the same ten members by hand is **19 lines**, and it also leaves **10 members** in its class
file; the two classes' method names are identical. A record brings no new mechanism — what
turns up at the class-file level is an ordinary class. What it brings is that **the mistakes
possible in writing these ten members by hand disappear**: when a component is added, it is
possible to forget it in a hand-written equality method; not so in the produced one.

The Java Fundamentals course measured this same thing for enumerations too, and showed that a
declaration writing only two names in the source carries fields and methods absent from the
source in its class file. The two forms use the same mechanism; what differs is what they
produce, not whether they produce.

The three trials in the bottom section show one more side of what is produced. A record
**cannot be extended**: the class carries the `final` flag, and a class extending it does not
compile. Implementing an interface, though, is free. Reassigning its field does not compile
either, because all the fields are `final` — the restriction the previous lesson measured is
enforced here without ever being written in the source.

#### Which Side Do the Produced Members Leave the Decision To

A record's not being extendable brings the course's question to an odd place. Since a record
type cannot have a subtype, a name declared with that type will always have the same runtime
type. Does the question, then, lose its meaning for the produced members?

```java
// Side.java — which side do the produced members leave the decision to
import java.util.*;

public class Side {
    interface Carrier { }
    record Entry(String name, int count) implements Carrier { }

    enum Operation {
        ADD { @Override int apply(int a) { return a + 1; } },
        KEEP;
        int apply(int a) { return a; }
    }

    public static void main(String[] args) {
        Carrier t = new Entry("bolt", 12);
        Object n = t;
        System.out.println("declared type Object, toString      : " + n);
        Entry a = new Entry("bolt", 12), b = new Entry("bolt", 12);
        System.out.println("a == b                               : " + (a == b));
        System.out.println("a.equals(b)                          : " + a.equals(b));
        System.out.println("hash codes equal                     : " + (a.hashCode() == b.hashCode()));
        Map<Entry, String> m = new HashMap<>();
        m.put(a, "first");
        System.out.println("read with key b                      : " + m.get(b));
        System.out.println();
        for (Operation i : Operation.values())
            System.out.printf("%-5s class is Operation itself: %-5s | declaring type: %s | apply(1)=%d%n",
                    i, i.getClass() == Operation.class, i.getDeclaringClass().getSimpleName(), i.apply(1));
        System.out.println("Operation.ADD and valueOf(\"ADD\") same object: "
                + (Operation.ADD == Operation.valueOf("ADD")));
    }
}
```

```
declared type Object, toString      : Entry[name=bolt, count=12]
a == b                               : false
a.equals(b)                          : true
hash codes equal                     : true
read with key b                      : first

ADD   class is Operation itself: false | declaring type: Operation | apply(1)=2
KEEP  class is Operation itself: true  | declaring type: Operation | apply(1)=1
Operation.ADD and valueOf("ADD") same object: true
```

The first line answers the question. The declared type is `Object`, and `Object`'s own
toString method does not give what the record gives; the printed text, though, is exactly the
form the record produces. The produced `toString` is an **overriding**, and like every
overriding, leaves the decision to the runtime type. Because a record cannot be extended, that
type is the **only known type** — but the decision still comes from that side. Not being
extendable does not change which side the decision comes from; it makes the answer
**knowable in advance**.

The next four lines show what the produced equality is. `a` and `b` are two separately
constructed objects, and `==` gives **false**: identity is separate. `equals`, though, gives
**true**, because the produced implementation compares the components. The hash values are
equal too, and the observable consequence of this is the last line: a map entry added with `a`
can be read with `b`. These three lines, where identity and equality come apart, are the
definition of how a data carrier should behave — and not a single character of it was written
in the source.

For an enumeration, the same question is asked from the opposite end. `ADD` and `KEEP` share
the same declared type, but `ADD`'s runtime class is **not the enumeration itself**: a
constant with its own body written takes its own class. For `KEEP`, the two are the same. The
two constants give separate answers to the same method call — **2** and **1** — and what
gives the answer, again, is the runtime type. The declaring type stays `Operation` for both;
this shows that, at the point where the declared type and the runtime type come apart, which
set a constant belongs to is not lost. The last line, in turn, measures the set's being fixed:
a constant found by name is the same object as the one found with `==`, because there is
exactly one of every constant.

Why the two forms sit side by side in this lesson is read from here. A record is written for
carriers whose **count is unknown**, and leaves each one's identity to its value; an
enumeration defines a set whose **count is known**, and keeps each one's identity in itself.
In one, `equals` can be true while `==` is false; in the other, the two always answer
together. What they share is the source of their produced members: both declarations have the
compiler produce a surface, and the produced surface's decision comes from the runtime type.

#### A Bounding Measurement: A Record Is Shallowly Immutable

All the produced fields were `final`, and the produced equality compared components. Read
together, these two facts make a record look "immutable." The boundary sits exactly here.

```java
// Shallow.java — a record is shallowly immutable
import java.util.Arrays;

public class Shallow {
    record Batch(String name, int[] counts) { }

    record Safe(String name, int[] counts) {
        Safe { counts = counts.clone(); }
    }

    public static void main(String[] args) {
        int[] outside = {1, 2, 3};
        Batch p = new Batch("bolt", outside);
        Safe g = new Safe("bolt", outside);
        System.out.println("after construction  : " + Arrays.toString(p.counts())
                + " | cloning: " + Arrays.toString(g.counts()));
        outside[0] = 99;
        System.out.println("written from outside: " + Arrays.toString(p.counts())
                + " | cloning: " + Arrays.toString(g.counts()));
        System.out.println();
        Batch q = new Batch("bolt", new int[] {99, 2, 3});
        System.out.println("name fields equal   : " + p.name().equals(q.name()));
        System.out.println("array content equal : " + Arrays.equals(p.counts(), q.counts()));
        System.out.println("p.equals(q)         : " + p.equals(q));
    }
}
```

```
after construction  : [1, 2, 3] | cloning: [1, 2, 3]
written from outside: [99, 2, 3] | cloning: [1, 2, 3]

name fields equal   : true
array content equal : true
p.equals(q)         : false
```

After `p` was constructed, none of its members were ever written to; even so, its `counts`
component changed. The side making the change never even saw the record — it wrote to the
array it had in hand, and the record was holding that very same array. As measured in the
previous lesson, a `final` field protects only the binding; the object at the end of that
binding sits outside the restriction. A record applies that restriction per component, so its
protection is only as deep as the components' type — it is **shallow**.

The bottom three lines show the same boundary on the equality side. `p` and `q` now carry the
same name and an array with the same content; both comparisons give true. `p.equals(q)`, even
so, is **false**. The produced equality compares every component by that component's own
equality; an array's own equality is identity. Inside the same record type, one component
looks at value, the other at identity, and nowhere in the source is this distinction written.

Both defects have their remedy in the same place. The `Safe` record clones the array in its
canonical constructor and is unaffected by the outside change; the output's second line shows
this. What the compiler produces is a starting point, not a guarantee: the produced members
inherit the components' behavior, and fixing it is left to whoever writes it.

#### Summary

- A one-line record declaration with an empty body produces **three fields** and **seven
  methods** in the class file; all three fields carry the `final` flag.
- The seven produced methods are three component readers, three shared-surface methods
  (`toString`, `hashCode`, `equals`), and the canonical constructor taking the components.
- A record cannot be extended, can implement an interface, and reassigning its field stops
  compilation.
- The produced members leave the decision to the **runtime type**: even when the declared
  type is `Object`, the printed text is the form the record produces. Not being extendable
  does not change which side the decision comes from; it makes the answer knowable in advance.
- For two separately constructed records, `==` is false, `equals` is true, and their hash
  values are equal; in an enumeration, a constant with its own body written takes its own
  class and gives a separate answer to the same call.
- A record's protection is shallow: an array component changes from outside, and the produced
  equality looks at that component's identity, not its value; cloning in the canonical
  constructor fixes both.

#### Next Step

Throughout this topic, the same question was measured: did the declared type give the
decision, or the runtime type? In the last two lessons, a third side joined the question — the
compiler itself. It produced the hidden fields, the constructor parameters, and a data
carrier's entire surface; everything it produced sat in the class file and could be read at
runtime. So what happens to another piece of information the compiler holds — **type
information itself**? Which type of element a container holds is written in the source, and
the compiler checks against that writing. The next topic measures how much of that information
survives at runtime: can two containers written with two separate type parameters be told
apart at runtime, and once the side doing the checking is gone, who pays the cost of writing
the wrong thing?

## Generics and Exceptions

What type parameters do at compile time and the gap type erasure leaves at runtime, separating read and write direction with bounded wildcards, the checked/unchecked exception distinction, automatic resource closing, and the decision of whether an error gets reported as an exception or as a return value.

### Generic Types

Source: https://academia.sh/en/courses/java-object-model/generic-types

The type parameter gets written to the class file in two separate places: erased in the descriptor the virtual machine reads, present in the signature attribute. The measurement shows the consequence — the check exists only at compile time, two generic types are the same class at runtime, and a write made through the raw type falls at the read site.

The previous lesson counted the members the compiler generates for a
record: accessors, equality, hash, and string conversion entered the
class file without being written in the source. If the compiler can
generate members, what does it do with the type information it holds —
does it write that somewhere too, or does it use it and let it go?

**Generics** were built in the TypeScript course around type
parameters and bounds, in terms of expressive power; this lesson does
not repeat that. The question here is this course's question: in the
writing `Store<Base>`, who decides using the `Base` information, and
when? The answer sits at an extreme in this topic — the entire
decision belongs to the **declared type**, and the runtime type has
nothing left to say. The measurement shows three consequences of this:
can two containers written with two separate type parameters be told
apart at runtime, where does the check get placed, and once the party
doing the checking is gone, who pays for a wrong write?

#### The Type Parameter Gets Written to the Class File Twice

- **GE1** — The measured source text is written within the lesson.
  Since we know what we wrote, every difference found in the class
  file is the compiler's decision.
- **GE2** — The Scale core comes from the shared setup. This lesson
  adds a single measurement to it: `signature`, which returns a
  member's signature attribute. The behavior of the existing
  measurements does not change.
- **GE3** — Descriptor and signature text get printed in their raw
  form from the class file; not abbreviated, not converted to a
  readable form.

```java
// Scale.java - shared measurement core: compiles a class file and reads it
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Scale {
    static ClassModel compile(String source, String className) throws Exception {
        Path dir = Files.createTempDirectory("scale");
        Path file = dir.resolve(className + ".java");
        Files.writeString(file, source);
        PrintWriter silent = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(silent, silent, "-d", dir.toString(), file.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return ClassFile.of().parse(dir.resolve(className + ".class"));
    }

    static List<String> instructions(String source, String className, String methodName) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(methodName))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        names.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return names;
    }

    static String signature(AttributedElement e) {
        return e.findAttribute(Attributes.signature())
                .map(a -> a.signature().stringValue()).orElse("-");
    }
}
```

```java
// Signature.java - where the type parameter sits in the class file
import java.lang.classfile.*;

public class Signature {
    static final String STORE = """
        class Store<T> {
            private T item;
            void put(T item) { this.item = item; }
            T get() { return item; }
        }
        """;

    public static void main(String[] args) throws Exception {
        ClassModel cm = Scale.compile(STORE, "Store");
        System.out.printf("%-14s %-26s %s%n", "member", "what the VM sees", "signature attribute");
        System.out.printf("%-14s %-26s %s%n", "Store class", "-", Scale.signature(cm));
        for (FieldModel f : cm.fields())
            System.out.printf("%-14s %-26s %s%n", "field " + f.fieldName().stringValue(),
                    f.fieldType().stringValue(), Scale.signature(f));
        for (MethodModel m : cm.methods())
            System.out.printf("%-14s %-26s %s%n", "method " + m.methodName().stringValue(),
                    m.methodType().stringValue(), Scale.signature(m));
    }
}
```

```
member         what the VM sees           signature attribute
Store class    -                          <T:Ljava/lang/Object;>Ljava/lang/Object;
field item     Ljava/lang/Object;         TT;
method <init>  ()V                        -
method put     (Ljava/lang/Object;)V      (TT;)V
method get     ()Ljava/lang/Object;       ()TT;
```

The middle column is **type erasure**. Every place `T` was written in
the source has become `Object` in the class file's descriptor: the
field is `Ljava/lang/Object;`, the `put` method takes an `Object`, the
`get` method returns an `Object`. The virtual machine looks at this
column when it calls a method; to it, `Store` is an ordinary
single-field class, and there is no such thing as `T`.

The right column, though, is **not erased**. The class's signature
writes that `T` exists and that its upper bound is `Object`; the
field's signature is `TT;`, that is, "type variable `T`"; `put` and
`get`'s signatures carry the name `T` too. The information written in
the source did not disappear, it got written to a **second place**.
The constructor has no signature, because no type parameter appears in
the constructor's definition.

This gives this topic's answer to the course's question. Only the
**compiler** reads the right column; the virtual machine **executes**
the left column. Every decision about the type parameter rests on a
text the runtime type never looks at.

#### The Check Goes on the Read Side, Not the Write Side

- **GE4** — The same write gets tried in three separate sources; two
  compile, one does not. The compiler's message text is not printed,
  only whether it compiled gets reported.

```java
// Check.java - what instruction a generic call leaves in the class file
public class Check {
    static final String TYPES = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Store<T> { private T item; void put(T o) { item = o; } T get() { return item; } }
        """;

    static final String USAGE = TYPES + """
        class Usage {
            static Base read(Store<Base> s) { return s.get(); }
            @SuppressWarnings("rawtypes")
            static Object rawRead(Store s) { return s.get(); }
            static void write(Store<Base> s, Derived d) { s.put(d); }
        }
        """;

    static final String WRITE =
            TYPES + "class Write { static void w(%s s) { s.put(new Derived()); } }";

    static void attempt(String label, String source) {
        try {
            Scale.compile(source, "Write");
            System.out.printf("%-30s -> compiled%n", label);
        } catch (Exception e) {
            System.out.printf("%-30s -> %s%n", label, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("read   Store<Base> : " + Scale.instructions(USAGE, "Usage", "read"));
        System.out.println("read   raw Store   : " + Scale.instructions(USAGE, "Usage", "rawRead"));
        System.out.println("write  Store<Base> : " + Scale.instructions(USAGE, "Usage", "write"));
        System.out.println();
        attempt("Store<Base> s; s.put(Derived)", WRITE.formatted("Store<Base>"));
        attempt("Store<String> s; s.put(Derived)", WRITE.formatted("Store<String>"));
        attempt("Store s (raw); s.put(Derived)", WRITE.formatted("Store"));
    }
}
```

```
read   Store<Base> : [aload_0, invokevirtual, checkcast, areturn]
read   raw Store   : [aload_0, invokevirtual, areturn]
write  Store<Base> : [aload_0, aload_1, invokevirtual, return]

Store<Base> s; s.put(Derived)  -> compiled
Store<String> s; s.put(Derived) -> did not compile: Write
Store s (raw); s.put(Derived)  -> compiled
```

The first two lines are separated by exactly one instruction. The read
through `Store<Base>` is **four** instructions, and the fourth is
`checkcast`; the same read through the raw type is **three**
instructions, with no check. The method called is the same method in
both cases — the class file has a single `get` that returns `Object`.
What makes the difference is not the called side, it is the
**calling side**: the compiler inserts a check to fit the read value
to the declared type.

The third line is this lesson's quietest measurement. The write is
**three** instructions and carries no check at all: load the
reference, load the value, call. A check gets placed at the read site,
not at the write site. The reason is in the lower table: the write is
**already checked at compile time**. Source that writes `Derived` into
a store named `Store<String>` does not compile. The decision comes
entirely from the declared type, and since compilation stops, no work
is left for runtime.

The lower table's third row writes down the price paid. When the same
store gets named with a **raw type**, the same write compiles. The
compiler produces a warning here, but the warning does not stop
compilation; once the declared type is gone, there is no party left to
check.

#### A Bound Changes the Erased Type

- **GE5** — All five attempts are built on the same bounded class;
  only the tried line changes, the types around it stay fixed.

```java
// Bound.java - a bound changes the type that gets erased
import java.lang.classfile.*;

public class Bound {
    static final String BOUNDED = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Bounded<T extends Base> {
            private T item;
            void put(T item) { this.item = item; }
            T get() { return item; }
        }
        """;

    static final String READING = BOUNDED + """
        class Reading {
            static Base baseRead(Bounded<Base> b) { return b.get(); }
            static Derived derivedRead(Bounded<Derived> b) { return b.get(); }
        }
        """;

    static void attempt(String label, String declaration, String body) {
        try {
            Scale.compile(BOUNDED + "class Forbidden" + declaration + " { " + body + " }", "Forbidden");
            System.out.printf("%-38s -> compiled%n", label);
        } catch (Exception e) {
            System.out.printf("%-38s -> %s%n", label, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        ClassModel cm = Scale.compile(BOUNDED, "Bounded");
        System.out.printf("%-14s %-26s %s%n", "member", "what the VM sees", "signature attribute");
        System.out.printf("%-14s %-26s %s%n", "Bounded class", "-", Scale.signature(cm));
        for (FieldModel f : cm.fields())
            System.out.printf("%-14s %-26s %s%n", "field " + f.fieldName().stringValue(),
                    f.fieldType().stringValue(), Scale.signature(f));
        for (MethodModel m : cm.methods())
            if (!m.methodName().stringValue().equals("<init>"))
                System.out.printf("%-14s %-26s %s%n", "method " + m.methodName().stringValue(),
                        m.methodType().stringValue(), Scale.signature(m));

        System.out.println();
        System.out.println("Bounded<Base> read    : " + Scale.instructions(READING, "Reading", "baseRead"));
        System.out.println("Bounded<Derived> read  : " + Scale.instructions(READING, "Reading", "derivedRead"));

        System.out.println();
        attempt("<T> calling o.name()", "<T>", "String s(T o) { return o.name(); }");
        attempt("<T extends Base> calling o.name()", "<T extends Base>", "String s(T o) { return o.name(); }");
        attempt("new T[3]", "<T extends Base>", "Object[] d() { return new T[3]; }");
        attempt("x instanceof Bounded<Base>", "",
                "boolean t(Object x) { return x instanceof Bounded<Base>; }");
        attempt("x instanceof Bounded<?>", "",
                "boolean t(Object x) { return x instanceof Bounded<?>; }");
    }
}
```

```
member         what the VM sees           signature attribute
Bounded class  -                          <T:LBase;>Ljava/lang/Object;
field item     LBase;                     TT;
method put     (LBase;)V                  (TT;)V
method get     ()LBase;                   ()TT;

Bounded<Base> read    : [aload_0, invokevirtual, areturn]
Bounded<Derived> read  : [aload_0, invokevirtual, checkcast, areturn]

<T> calling o.name()                   -> did not compile: Forbidden
<T extends Base> calling o.name()      -> compiled
new T[3]                               -> did not compile: Forbidden
x instanceof Bounded<Base>             -> did not compile: Forbidden
x instanceof Bounded<?>                -> compiled
```

The descriptor column changed: a **bounded** type parameter erases not
to `Object`, but to its **bound**. The field is `LBase;`, `put` takes
a `Base`, `get` returns a `Base`. The signature column, though, stays
the same — it still reads `TT;`, and the class signature carries the
bound too.

This has its first consequence in the lower table's first two rows.
Source calling `o.name()` through an unbounded type parameter does not
compile, because the erased type is `Object`, and `Object` has no such
method. The same line compiles once a bound gets added. **A bound is
not documentation**; it changes the descriptor in the class file, and
widens the set of methods a call can bind to.

The second consequence is in the read lines and completes the previous
section's measurement. There is **no** `checkcast` in the read through
`Bounded<Base>`, and there **is** one in the read through
`Bounded<Derived>`. A check does not get added to every generic read;
it gets added only when the declared type and the erased type
**diverge**. In the first measurement, `Store<T>` erased to `Object`,
and a `Base` read diverged from that; here, since the erased type is
already `Base`, there is nothing left to add.

The last three lines are erasure's direct prohibitions. `new T[3]`
does not compile: an array carries its element type within itself and
checks it on every write — measured in the Java Fundamentals course —
but since no type called `T` exists at runtime, there is nothing to
carry. `x instanceof Bounded<Base>` does not compile either, while
`x instanceof Bounded<?>` does. The only question that can be asked of
the virtual machine is the raw class; a **wildcard** means exactly
"do not ask about the type parameter."

#### Two Generic Types Are the Same Class at Runtime

- **GE6** — Identity gets asked only with `==`; no identity number is
  printed.
- **GE7** — The raw-typed write's result is not written as an
  assertion, it is measured with `instanceof`; only the class name
  gets printed from the caught exception.

```java
// Erasure.java - what type erasure leaves behind at runtime
import java.util.Arrays;

class Base {
    String name() { return "base method"; }
}

class Derived extends Base {
    @Override String name() { return "derived method"; }
}

class Store<T> {
    private T item;
    void put(T item) { this.item = item; }
    T get() { return item; }
}

public class Erasure {
    static Store<Base> field;

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) throws Exception {
        Store<Base> carriers = new Store<>();
        Store<String> texts = new Store<>();
        System.out.println("Store<Base> and Store<String> same class : "
                + (carriers.getClass() == texts.getClass()));
        System.out.println("type parameters at runtime               : "
                + Arrays.toString(Store.class.getTypeParameters()));

        carriers.put(new Derived());
        System.out.println("runtime type of the item read            : "
                + carriers.get().getClass().getSimpleName());

        Store raw = carriers;
        raw.put("north");
        System.out.println("was the raw-type write stopped           : "
                + (carriers.get() instanceof Base ? "yes" : "no"));
        try {
            Base b = carriers.get();
            System.out.println("read result                               : " + b.name());
        } catch (ClassCastException e) {
            System.out.println("error not at the write site, at the READ site : "
                    + e.getClass().getSimpleName());
        }
        System.out.println("field's type read from the signature attribute : "
                + Erasure.class.getDeclaredField("field").getGenericType());
    }
}
```

```
Store<Base> and Store<String> same class : true
type parameters at runtime               : [T]
runtime type of the item read            : Derived
was the raw-type write stopped           : no
error not at the write site, at the READ site : ClassCastException
field's type read from the signature attribute : Store<Base>
```

The first two lines are the descriptor column's consequence.
`Store<Base>` and `Store<String>` are the **same** class at runtime,
and that class's type parameter sits under the name `T` alone, with no
value. Two separate types were written in the source; there is a
**single** class on the heap.

The third line sharpens the distinction. The object read from the
store has the runtime type `Derived` — that is, the object's own class
is known and not hidden. What is **unknown** is what the
**container** holds. An object's type exists at runtime; a
container's type parameter does not.

The fourth and fifth lines measure the price. The string written
through the raw type was **never stopped**: the store accepted it, and
the item in the store is no longer a `Base`. The error fell not at the
line where the write happened, but at the line where the **read**
happened — because the only check is the `checkcast` instruction
sitting there. This is the price a programmer looking at the wrong
side pays: the line producing the defect and the line where the defect
shows up sit in **separate places**, and the distance between them can
be as long as the program itself.

#### What Gets Erased Is Not the Information, It Is the Check

The last line is this lesson's boundary measurement, and it flips the
thesis. A field declared as `Store<Base>` has its generic type
**readable** at runtime: `Store<Base>`. The information was not
erased, it sits in the signature attribute, and it can be retrieved
from there.

What gets erased, then? The first measurement's two columns give the
answer. The descriptor the virtual machine executes reads `Object`;
the virtual machine does not look at the signature attribute when it
checks a call. Type information sits in the class file as **data**,
but it does not get used as a **check**. The name "type erasure," for
this reason, describes not the information disappearing, but the
**check that rests on that information** disappearing. Reading a
field's signature and stopping a write are separate things: the first
is reading a text back, the second is work that never happens at
runtime at all.

#### Summary

- The type parameter gets written to the class file in two separate
  places: erased as `Object` in the descriptor the virtual machine
  reads, written under the name `T` in the signature attribute.
- A generic read call adds a `checkcast` instruction in the class
  file; the same read through the raw type has no such instruction.
  The check sits on the calling side, not the called side.
- A generic write call leaves no check instruction, because the write
  is checked at compile time: source writing `Derived` into a
  `Store<String>` does not compile.
- A bounded type parameter erases to its bound, not to `Object`; this
  both opens up calling a method through `T`, and removes the
  `checkcast` instruction once the declared type and the erased type
  coincide.
- At runtime, `Store<Base>` and `Store<String>` are the same class; a
  raw-type write goes unstopped, and the error falls not at the write
  site, but at the read site.
- Boundary measurement: a field's generic type can be read back at
  runtime from the signature attribute. What gets erased is not the
  information, it is the check resting on that information.

#### Next Step

This lesson named a store with a single type, and a difference in
checking came out between writing to that type and reading from it: an
instruction got added to the read, not to the write. The same
difference can be asked at the **type** level too. If, instead of
`Store<Base>`, we want to write "a store holding `Base` or any type
below it," does reading from that store stay as open as writing to it?
The next lesson tries four separate container forms against two
operations and counts how many of the pairs compile.

### Bounded Wildcards

Source: https://academia.sh/en/courses/java-object-model/bounded-wildcards

Four container forms and two operations give eight pairs; only four of them compile. Covariance opens reading, contravariance opens writing, and the two never open together; an unbounded wildcard, in turn, allows only writing a null value. The entire direction bound lives in the signature attribute, and leaves no trace at runtime.

The previous lesson named a store with a single type and measured that
a check instruction got added to the read, not to the write. The write
was already closed off at compile time. The same question can be asked
at the type level too: if, instead of `Store<Base>`, we want to write
"a store holding `Base` or any type below it," does reading from that
store stay as open as writing to it?

This lesson tries four container forms against two operations and
counts how many pairs compile. This course's question applies here
too, but the direction of the answer is known in advance: the bound a
wildcard places belongs only to the declared type. The measurement
shows both how many gates that bound closes, and what is left of it at
runtime.

#### Four Container Forms, Two Operations

- **GE8** — The measured source is written within the lesson; only
  the container form and the tried line change, the surrounding types
  are the same in every attempt.
- **GE9** — The Scale core comes from the shared setup; the previous
  lesson's two measurements, instruction names and the signature
  attribute, are used as they are.
- **GE10** — An attempt either compiles or does not; the compiler's
  message text is not printed, because what gets measured is not the
  message's content, it is whether the gate is open.

```java
// Scale.java - shared measurement core: compiles a class file and reads it
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Scale {
    static ClassModel compile(String source, String className) throws Exception {
        Path dir = Files.createTempDirectory("scale");
        Path file = dir.resolve(className + ".java");
        Files.writeString(file, source);
        PrintWriter silent = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(silent, silent, "-d", dir.toString(), file.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return ClassFile.of().parse(dir.resolve(className + ".class"));
    }

    static List<String> instructions(String source, String className, String methodName) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(methodName))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        names.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return names;
    }

    static String signature(AttributedElement e) {
        return e.findAttribute(Attributes.signature())
                .map(a -> a.signature().stringValue()).orElse("-");
    }
}
```

```java
// Direction.java - four container forms, two operations: how many pairs compile
import java.lang.classfile.*;

public class Direction {
    static final String TYPES = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Store<T> { private T item; void put(T o) { item = o; } T get() { return item; } }
        """;

    static final String PATTERN = TYPES + "class Trial { static void y(%s d) { %s } }";

    static final String[] CONTAINERS =
            {"Store<Base>", "Store<? extends Base>", "Store<? super Derived>", "Store<?>"};

    static final String READ = "Base u = d.get();";
    static final String WRITE = "d.put(new Derived());";

    static boolean compiles(String container, String operation) {
        try {
            Scale.compile(PATTERN.formatted(container, operation), "Trial");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.printf("%-22s %-18s %s%n", "container form", "Base u = d.get()", "d.put(new Derived())");
        int passed = 0;
        for (String container : CONTAINERS) {
            boolean r = compiles(container, READ), w = compiles(container, WRITE);
            passed += (r ? 1 : 0) + (w ? 1 : 0);
            System.out.printf("%-22s %-18s %s%n", container,
                    r ? "compiled" : "did not compile", w ? "compiled" : "did not compile");
        }
        System.out.println("compiled count of eight pairs: " + passed);
    }
}
```

```
container form         Base u = d.get()   d.put(new Derived())
Store<Base>            compiled           compiled
Store<? extends Base>  compiled           did not compile
Store<? super Derived> did not compile    compiled
Store<?>               did not compile    did not compile
compiled count of eight pairs: 4
```

Four of the eight pairs compile, four do not. The first row is the
**reference row**: in an exactly named store, both gates are open. The
next three rows close the gates one by one.

The writing `Store<? extends Base>` establishes **covariance**: a
store of any type under `Base` fits this name. Reading is open,
because whatever the container holds is at least a `Base`. Writing is
closed, because it is not known which type the container is actually a
store of; it could be `Store<Derived>`, or the store of some other
type under `Base`. Writing a `Derived` would be wrong in the second
case, and since the compiler does not know which it is, it closes
both.

The writing `Store<? super Derived>` establishes **contravariance**,
and **swaps** the two gates. Writing is open: the container is the
store of some type above `Derived`, and a `Derived` can be written to
the store of any such type. Reading is closed, because the container
could be `Store<Object>`, and the read value being a `Base` is not
guaranteed.

A measured rule follows from this: **to read from a container, write
an upper bound; to write to a container, write a lower bound; the two
never open together.** What a wildcard earns is a wider set of
accepted types; what it costs is one of the two operations closing.

#### The Direction Bound Lives Only in the Signature

```java
// Trace.java - does a wildcard leave a trace in the class file
import java.lang.classfile.*;

public class Trace {
    static final String TYPES = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Store<T> { private T item; void put(T o) { item = o; } T get() { return item; } }
        """;

    static final String PATTERN = TYPES + "class Trial { static void y(%s d) { %s } }";

    static final String COVARIANT = PATTERN.formatted("Store<? extends Base>", "Base u = d.get();");
    static final String UNBOUNDED = PATTERN.formatted("Store<?>", "Object o = d.get();");

    public static void main(String[] args) throws Exception {
        for (MethodModel m : Scale.compile(COVARIANT, "Trial").methods())
            if (m.methodName().stringValue().equals("y"))
                System.out.println("y descriptor : " + m.methodType().stringValue()
                        + "   signature: " + Scale.signature(m));
        System.out.println("read Store<? extends Base> : "
                + Scale.instructions(COVARIANT, "Trial", "y"));
        System.out.println("read Store<?>               : "
                + Scale.instructions(UNBOUNDED, "Trial", "y"));
    }
}
```

```
y descriptor : (LStore;)V   signature: (LStore<+LBase;>;)V
read Store<? extends Base> : [aload_0, invokevirtual, checkcast, astore_1, return]
read Store<?>               : [aload_0, invokevirtual, astore_1, return]
```

This measurement says where the bound lives. The descriptor the
virtual machine reads is `(LStore;)V`: the parameter has neither a
type parameter nor a wildcard. The signature attribute, though, has
the **entire** bound written — the plus sign in `+LBase;` marks an
upper-bounded wildcard. The previous lesson's distinction shows up
here once more: the information sits in the class file, but the
virtual machine does not look at it.

The instruction lines point the same way. The read through the
covariant view carries a `checkcast` instruction, because the declared
type `Base` diverges from the erased type `Object`. The read as
`Object` through the unbounded view carries no check. Not a single
instruction belongs to the direction bound; there is no mark in the
class file saying "cannot be written here," because that decision was
made at compile time and ended there.

#### An Unbounded Wildcard Accepts Only a Null Value

- **GE11** — In the second table, reading happens as Object and the
  written value is null; the container forms are the same as the
  first table's.

```java
// Null.java - what an unbounded wildcard allows
public class Null {
    static final String TYPES = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Store<T> { private T item; void put(T o) { item = o; } T get() { return item; } }
        """;

    static final String PATTERN = TYPES + "class Trial { static void y(%s d) { %s } }";

    static final String[] CONTAINERS =
            {"Store<Base>", "Store<? extends Base>", "Store<? super Derived>", "Store<?>"};

    static String attempt(String container, String operation) {
        try {
            Scale.compile(PATTERN.formatted(container, operation), "Trial");
            return "compiled";
        } catch (Exception e) {
            return "did not compile";
        }
    }

    public static void main(String[] args) {
        System.out.printf("%-24s %-18s %s%n", "container form", "Object o = d.get()", "d.put(null)");
        for (String container : CONTAINERS)
            System.out.printf("%-24s %-18s %s%n", container,
                    attempt(container, "Object o = d.get();"), attempt(container, "d.put(null);"));
        System.out.println();
        System.out.printf("%-26s %s%n", "Store<?>    d.put(d.get())", attempt("Store<?>", "d.put(d.get());"));
        System.out.printf("%-26s %s%n", "Store<Base> d.put(d.get())", attempt("Store<Base>", "d.put(d.get());"));
    }
}
```

```
container form           Object o = d.get() d.put(null)
Store<Base>              compiled           compiled
Store<? extends Base>    compiled           compiled
Store<? super Derived>   compiled           compiled
Store<?>                 compiled           compiled

Store<?>    d.put(d.get()) did not compile
Store<Base> d.put(d.get()) compiled
```

In the second table, **eight of eight** pairs compile, and this is
this lesson's boundary measurement. None of the first table's four
closed gates was actually **locked**; two were only **narrowed**, and
two were only closed for a **specific value**.

Reading never closes in any form. Reading through `Store<? super
Derived>` and `Store<?>` had fallen in the first table, because the
result was being assigned to a `Base`. Read as `Object`, all four
compile. An unbounded wildcard does not forbid reading, it drops the
read value's declared type to `Object`.

Writing never fully closes either. All four container forms accept a
**null** value, because null fits every reference type and raises no
question about which type the container is a store of. This gives
`Store<?>`'s measured definition: **reading only as `Object`, writing
only with null.** An unbounded wildcard does not state a type, it
states that it will not ask the type question.

The last two lines are the same rule at its narrowest point. On
`Store<Base>`, the writing `d.put(d.get())` — reading from a container
and writing straight back into it — compiles. The same line does
**not** compile on `Store<?>` — since the read value's type is
unknown, even coming from the same container does not make the write
valid. Here, the compiler tracks not the type's **identity**, only its
**name**.

#### What Separating Direction Buys on the Calling Side

- **GE12** — Nine calls each get tried for two signatures; the trial
  set is the same for both, and only the called method's parameter
  form changes.

```java
// Copy.java - how many pairs separating direction opens on the calling side
public class Copy {
    static final String TYPES = """
        class Base { String name() { return "base method"; } }
        class Derived extends Base { @Override String name() { return "derived method"; } }
        class Store<T> { private T item; void put(T o) { item = o; } T get() { return item; } }
        """;

    static final String EXACT = "static void copy(Store<Base> target, Store<Base> source) { target.put(source.get()); }";
    static final String WILDCARD =
            "static void copy(Store<? super Base> target, Store<? extends Base> source) { target.put(source.get()); }";

    static final String[] STORES = {"Store<Object>", "Store<Base>", "Store<Derived>"};

    static int count(String signature) {
        int passed = 0;
        System.out.printf("%-14s %-12s %-12s %s%n", "target \\ source",
                STORES[0], STORES[1], STORES[2]);
        for (String target : STORES) {
            String[] cells = new String[STORES.length];
            for (int i = 0; i < STORES.length; i++) {
                String source = TYPES + "class C { " + signature + " static void c("
                        + target + " target, " + STORES[i] + " source) { copy(target, source); } }";
                try {
                    Scale.compile(source, "C");
                    cells[i] = "compiled";
                    passed++;
                } catch (Exception e) {
                    cells[i] = "-";
                }
            }
            System.out.printf("%-14s %-12s %-12s %s%n", target, cells[0], cells[1], cells[2]);
        }
        return passed;
    }

    public static void main(String[] args) {
        System.out.println("copy(Store<Base>, Store<Base>)");
        System.out.println("compiled count of nine pairs: " + count(EXACT));
        System.out.println();
        System.out.println("copy(Store<? super Base>, Store<? extends Base>)");
        System.out.println("compiled count of nine pairs: " + count(WILDCARD));
    }
}
```

```
copy(Store<Base>, Store<Base>)
target \ source Store<Object> Store<Base>  Store<Derived>
Store<Object>  -            -            -
Store<Base>    -            compiled     -
Store<Derived> -            -            -
compiled count of nine pairs: 1

copy(Store<? super Base>, Store<? extends Base>)
target \ source Store<Object> Store<Base>  Store<Derived>
Store<Object>  -            compiled     compiled
Store<Base>    -            compiled     compiled
Store<Derived> -            -            -
compiled count of nine pairs: 4
```

Same body, two separate parameter forms: the first accepts **one** of
nine call pairs, the second **four**. The two closed operations pay
off here. Since only reading happens from the source, an upper bound
can be put on it; since only writing happens to the target, a lower
bound can be put on it, and the method opens up to four separate
calls.

The empty cells are the same rule's measure too. `Store<Derived>`
cannot be the target, because a `Base` could get written there;
`Store<Object>` cannot be the source, because the value coming from it
is not known to be a `Base`. The widening is not arbitrary, it matches
the operation's direction exactly.

#### The Bound Does Not Exist at Runtime

- **GE13** — Object identity gets asked only with `==`; no identity
  number is printed.
- **GE14** — The raw-type write's result is not asserted, it is
  measured with `instanceof`.

```java
// Flow.java - does the direction bound exist at runtime
class Base {
    String name() { return "base method"; }
}

class Derived extends Base {
    @Override String name() { return "derived method"; }
}

class Store<T> {
    private T item;
    void put(T item) { this.item = item; }
    T get() { return item; }
}

public class Flow {
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) {
        Store<Derived> derivedStore = new Store<>();
        derivedStore.put(new Derived());

        Store<? extends Base> readable = derivedStore;
        Store<? super Derived> writable = derivedStore;
        Store<?> closed = derivedStore;
        System.out.println("four declared types, how many objects : "
                + ((readable == writable) && (writable == closed) && (closed == derivedStore) ? 1 : 2));
        System.out.println("object's runtime class                 : "
                + derivedStore.getClass().getSimpleName());

        System.out.println("read through covariant view            : " + readable.get().name());
        writable.put(new Derived());
        System.out.println("write through contravariant view       : " + derivedStore.get().name());

        Store raw = readable;
        raw.put(new Base());
        System.out.println("was a Base written through raw type    : "
                + (derivedStore.get() instanceof Derived ? "no" : "yes"));
        try {
            Derived a = derivedStore.get();
            System.out.println("read result                            : " + a.name());
        } catch (ClassCastException e) {
            System.out.println("error at the READ site                 : "
                    + e.getClass().getSimpleName());
        }
    }
}
```

```
four declared types, how many objects : 1
object's runtime class                 : Store
read through covariant view            : derived method
write through contravariant view       : derived method
was a Base written through raw type    : yes
error at the READ site                 : ClassCastException
```

The first two lines reduce this lesson's whole table to a single
sentence: four separate declared types, **one** object. The
container's runtime class is just `Store`; neither the type parameter
nor the direction bound is there. The read and write gates open and
close not on the object, but on the **name looking at it**.

The third and fourth lines confirm the open gates work. The fifth line
shows what the closed gate was closed **with**. Writing a `Base` to
the covariant view did not compile; once the same view gets named with
the raw type, the write **goes through**, and the item in the store is
no longer a `Derived`. The only party enforcing the direction bound is
the compiler; once that party is disabled, no one is left to enforce
it again.

The last line writes down where the price fell. The defect showed up
not at the line where the wrong type got written, but at the read
**expecting** a `Derived` — and this time there is also a change of
view in between: the writing code knew the container through its
covariant view, the failing code through its actual type.

#### Summary

- Four of the eight pairs of four container forms and two operations
  compile. A wildcard widens the accepted type set, and closes one of
  the two operations in return.
- Covariance (`? extends`) opens reading, closes writing; contravariance
  (`? super`) opens writing, drops reading to the `Object` level. The
  two never open together.
- Boundary measurement: no gate is actually locked. All four forms can
  be read as `Object`, and all four can have a null value written to
  them; this is the only value an unbounded wildcard allows writing.
- On `Store<?>`, writing what was read from a container back into that
  same container does not compile, while on `Store<Base>` it does: the
  compiler tracks the type's name, not its identity.
- The entire direction bound lives in the signature attribute. The
  descriptor carries no trace of the wildcard, not a single
  instruction gets produced for it, and four separate declared types
  are one object at runtime.

#### Next Step

Across two lessons, the gates the compiler closes got counted: writing
to the wrong type did not compile, writing to a covariant view did not
compile, writing back what got read from a container did not compile.
In every one of these, the compiler placed a **prohibition**. Java also
has something else the compiler imposes on source: a **requirement** —
a method cannot be written without declaring or handling certain
events. The next lesson measures that requirement — what exactly does
the distinction between a checked and an unchecked exception force at
compile time, and does the virtual machine check that requirement's
counterpart in the class file?

### Exception Hierarchy

Source: https://academia.sh/en/courses/java-object-model/exception-hierarchy

What the checked/unchecked exception distinction forces at compile time gets measured: only one of nine writings gets rejected, the declaration sits in the class file as an attribute, not an instruction, and the virtual machine never checks it. Which catch branch runs, though, is decided by the runtime type.

The previous two lessons counted the gates the compiler closes: writing
to the wrong type did not compile, writing to a covariant view did not
compile, writing back a value read from a container did not compile. In
each of these, the compiler placed a **prohibition**. Java also has
something else the compiler imposes on source: a **requirement** — a
method cannot be written without declaring or handling certain events.

That an exception is an object, that a catch takes a class, and how
many events the hierarchy holds got measured in the Python Fundamentals
course; that arithmetic is not repeated here. The question here is this
course's question, and it has two answers: the **declared type**
decides the requirement to **declare** an exception, while the
**runtime type** decides **which catch branch runs**. This lesson
measures both in the same place.

#### The One Thing the Compiler Forces

- **GE15** — Three exception kinds get defined in source within the
  lesson: a checked exception under `Exception`, an unchecked exception
  under `RuntimeException`, a severe error under `Error`.
- **GE16** — The three writing styles carry the same body; only the
  method's declaration and what surrounds the body change.
- **GE17** — The compiler's message text is not printed; what gets
  measured is whether the writing gets accepted.

```java
// Scale.java - shared measurement core: compiles a class file and reads it
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Scale {
    static ClassModel compile(String source, String className) throws Exception {
        Path dir = Files.createTempDirectory("scale");
        Path file = dir.resolve(className + ".java");
        Files.writeString(file, source);
        PrintWriter silent = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(silent, silent, "-d", dir.toString(), file.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return ClassFile.of().parse(dir.resolve(className + ".class"));
    }

    static List<String> instructions(String source, String className, String methodName) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(methodName))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        names.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return names;
    }

    static String shortName(String internalName) { return internalName.substring(internalName.lastIndexOf('/') + 1); }
}
```

```java
// Mandatory.java - three exception kinds, three writing styles
public class Mandatory {
    static final String TYPES = """
        class BaseException extends Exception { BaseException(String m) { super(m); } }
        class SilentException extends RuntimeException { SilentException(String m) { super(m); } }
        class SevereError extends Error { SevereError(String m) { super(m); } }
        """;

    static final String[] KINDS = {"BaseException", "SilentException", "SevereError"};

    static final String WITHOUT_DECLARING = "class C { static void f() { throw new %1$s(\"event\"); } }";
    static final String WITH_DECLARATION =
            "class C { static void f() throws %1$s { throw new %1$s(\"event\"); } }";
    static final String WITH_CATCH =
            "class C { static void f() { try { throw new %1$s(\"event\"); } catch (%1$s e) { } } }";

    static String attempt(String pattern, String kind) {
        try {
            Scale.compile(TYPES + pattern.formatted(kind), "C");
            return "compiled";
        } catch (Exception e) {
            return "did not compile";
        }
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-17s %-17s %s%n",
                "exception kind", "without declaring", "with declaration", "with catch");
        int passed = 0;
        for (String k : KINDS) {
            String a = attempt(WITHOUT_DECLARING, k), b = attempt(WITH_DECLARATION, k), c = attempt(WITH_CATCH, k);
            for (String s : new String[] {a, b, c}) if (s.equals("compiled")) passed++;
            System.out.printf("%-16s %-17s %-17s %s%n", k, a, b, c);
        }
        System.out.println("compiled count of nine writings: " + passed);
    }
}
```

```
exception kind   without declaring with declaration  with catch
BaseException    did not compile   compiled          compiled
SilentException  compiled          compiled          compiled
SevereError      compiled          compiled          compiled
compiled count of nine writings: 8
```

Eight of nine writings compile. The one rejected writing sits in the
table's top-left corner: a method that throws a checked exception and
neither declares nor catches it. This is the entire thing a checked
exception forces at compile time — **making a choice**. There is no
difference to the compiler between declaring and catching; both close
the same gate, and both columns are equally valid.

The second and third rows are the other face of the distinction. For
the unchecked exception and the severe error, **all three** writings
compile: they can be declared, they can be caught, but neither is
required. An unchecked exception can leave a method without leaving a
trace anywhere in the source.

Two sentences follow from this. First: the checked/unchecked
distinction is not a distinction of **behavior**; both get thrown, both
get caught, both fold the call stack upward. The distinction shows up
only in whether the compiler **accepts a writing**. Second: this
requirement looks at the **declared type**. The compiler does not learn
what a body actually throws by running it; it reads the
**declarations** of the methods it calls.

#### A Declaration Is an Attribute, Not an Instruction

- **GE18** — Both sources carry a single method, and their bodies are
  the same shape; only the kind of thrown exception changes.

```java
// Declaration.java - where the checked-exception declaration sits in the class file
import java.lang.classfile.*;
import java.lang.classfile.attribute.ExceptionsAttribute;
import java.util.List;

public class Declaration {
    static final String TYPES = """
        class BaseException extends Exception { BaseException(String m) { super(m); } }
        class SilentException extends RuntimeException { SilentException(String m) { super(m); } }
        """;

    static final String DECLARING = TYPES
            + "class C { static void f() throws BaseException { throw new BaseException(\"event\"); } }";
    static final String NOT_DECLARING = TYPES
            + "class C { static void f() { throw new SilentException(\"event\"); } }";

    static void report(String label, String source) throws Exception {
        for (MethodModel m : Scale.compile(source, "C").methods())
            if (m.methodName().stringValue().equals("f"))
                System.out.printf("%-14s descriptor %-6s exceptions attribute %s%n", label,
                        m.methodType().stringValue(),
                        m.findAttribute(Attributes.exceptions())
                                .map(ExceptionsAttribute::exceptions).orElse(List.of())
                                .stream().map(e -> Scale.shortName(e.asInternalName())).toList());
    }

    public static void main(String[] args) throws Exception {
        report("checked", DECLARING);
        report("unchecked", NOT_DECLARING);
        System.out.println();
        System.out.println("checked throw    : " + Scale.instructions(DECLARING, "C", "f"));
        System.out.println("unchecked throw  : " + Scale.instructions(NOT_DECLARING, "C", "f"));
    }
}
```

```
checked        descriptor ()V    exceptions attribute [BaseException]
unchecked      descriptor ()V    exceptions attribute []

checked throw    : [new, dup, ldc, invokespecial, athrow]
unchecked throw  : [new, dup, ldc, invokespecial, athrow]
```

The two methods' descriptors are **the same**: `()V`. A method's
identity as the virtual machine sees it is made of its parameter types
and return type; the exceptions it might throw do not enter there. The
declaration sits in a separate **attribute**, and for the unchecked
exception, that attribute is empty.

The lower lines are this fact's counterpart on the code side: the two
throws are **the exact same five instructions**. Create the object,
duplicate, load the message, call the constructor, throw. In the class
file's executed part, there is no difference between a checked and an
unchecked exception. The difference sits only in the attribute the
compiler reads — just like a type parameter sitting only in the
signature attribute. The pattern measured in this course's first lesson
shows up here once more: **information gets written into the class
file as data, not as a check.**

#### The Virtual Machine Does Not Look at the Declaration

- **GE19** — The measurement is built within the language itself,
  using no outside tool; the only information printed about the
  exception object is its class name.

```java
// Hidden.java - does the virtual machine check the declaration
class BaseException extends Exception {
    BaseException(String m) { super(m); }
}

public class Hidden {
    @SuppressWarnings("unchecked")
    static <T extends Throwable> void raise(Throwable t) throws T {
        throw (T) t;
    }

    static void undeclared() {
        Hidden.<RuntimeException>raise(new BaseException("event"));
    }

    public static void main(String[] args) {
        try {
            undeclared();
            System.out.println("no exception came out");
        } catch (Throwable t) {
            System.out.println("came out of the undeclared method      : " + t.getClass().getSimpleName());
            System.out.println("is it a checked exception               : "
                    + (t instanceof RuntimeException || t instanceof Error ? "no" : "yes"));
            System.out.println("did the virtual machine block the path  : no");
        }
    }
}
```

```
came out of the undeclared method      : BaseException
is it a checked exception               : yes
did the virtual machine block the path  : no
```

The `undeclared` method declares no exception, and its exceptions
attribute is empty. Despite this, a **checked** exception comes out of
that method, and nothing stops this path throughout the run.

How this happens joins the two halves of this topic. The `raise` method
is declared with the type parameter `T`; at the call site,
`RuntimeException` gets substituted for `T`, so to the compiler this
method throws an unchecked exception. The cast in the body, though,
happens to `T`, and because of type erasure, that cast leaves **no
check** in the class file. The guarantee the compiler builds with the
declaration slips through the gap type-parameter erasure opens up.

The rule the measurement states: a checked-exception declaration is
**a contract between compilations**, not a runtime guarantee. The
virtual machine never checks whether an exception coming out of a
method is written in that method's declaration. By this course's
measure, this is a pure **declared-type** decision, and the runtime
type has nothing to say about it.

#### A Declaration Can Narrow, Not Widen

- **GE20** — In the five attempts, only the subclass's declaration
  changes; the superclass's declaration, the exception types, and the
  calling code are the same in every attempt.

```java
// Widen.java - can an overriding method widen the declaration
public class Widen {
    static final String PATTERN = """
        class BaseException extends Exception { BaseException(String m) { super(m); } }
        class DerivedException extends BaseException { DerivedException(String m) { super(m); } }
        class IndependentException extends Exception { IndependentException(String m) { super(m); } }
        class Base { void f() throws BaseException { } }
        class Derived extends Base { @Override void f() %s { } }
        class C { static void c(Base u) { try { u.f(); } catch (Exception e) { } } }
        """;

    static void attempt(String declaration) {
        String label = declaration.isEmpty() ? "(no declaration)" : declaration;
        try {
            Scale.compile(PATTERN.formatted(declaration), "C");
            System.out.printf("Derived.f() %-28s -> compiled%n", label);
        } catch (Exception e) {
            System.out.printf("Derived.f() %-28s -> did not compile%n", label);
        }
    }

    public static void main(String[] args) throws Exception {
        for (String d : new String[] {"throws BaseException", "throws DerivedException", "",
                "throws IndependentException", "throws RuntimeException"})
            attempt(d);

        System.out.println();
        String call = """
            class BaseException extends Exception { BaseException(String m) { super(m); } }
            class Base { void f() throws BaseException { } }
            class Derived extends Base { @Override void f() { } }
            class C { static void c() { %s.f(); } }
            """;
        for (String declared : new String[] {"new Base()", "new Derived()"}) {
            String label = declared.equals("new Base()") ? "Base" : "Derived";
            try {
                Scale.compile(call.formatted(declared), "C");
                System.out.printf("declared type %-8s call unhandled -> compiled%n", label);
            } catch (Exception e) {
                System.out.printf("declared type %-8s call unhandled -> did not compile%n", label);
            }
        }
    }
}
```

```
Derived.f() throws BaseException         -> compiled
Derived.f() throws DerivedException      -> compiled
Derived.f() (no declaration)             -> compiled
Derived.f() throws IndependentException  -> did not compile
Derived.f() throws RuntimeException      -> compiled

declared type Base     call unhandled -> did not compile
declared type Derived  call unhandled -> compiled
```

The upper table's rule runs one direction. An overriding method can
declare exactly the exception the superclass declares, can declare a
subtype of it, can declare nothing — but it **cannot add another
checked exception**. Declaring an unchecked exception is free in every
case, because there was never any requirement for it to begin with.

The lower two lines measure this rule's reasoning, and answer this
course's question right at its center. `Derived`'s method declares no
exception. The same object, named as `Derived`, compiles with the call
unhandled; named as `Base`, it does **not** compile. The object is the
same, the body that runs is the same, the result is the same — the only
thing that changes is the **name's declared type**. Since the compiler
reads the handling requirement only from there, letting the subclass's
declaration widen would make no call written through `Base`
trustworthy.

#### The Two Sides of a Catch

```java
// Catch.java - the two sides of catch: which branch can be written, which branch runs
class BaseException extends Exception {
    BaseException(String m) { super(m); }
}

class DerivedException extends BaseException {
    DerivedException(String m) { super(m); }
}

public class Catch {
    static final String PATTERN = """
        class BaseException extends Exception { BaseException(String m) { super(m); } }
        class DerivedException extends BaseException { DerivedException(String m) { super(m); } }
        class IndependentException extends Exception { IndependentException(String m) { super(m); } }
        class C {
            static void raise() throws BaseException { throw new DerivedException("event"); }
            static void y() throws BaseException { try { raise(); } catch (%s e) { } }
        }
        """;

    static void attempt(String branch) {
        try {
            Scale.compile(PATTERN.formatted(branch), "C");
            System.out.printf("catch (%-19s -> compiled%n", branch + " e)");
        } catch (Exception e) {
            System.out.printf("catch (%-19s -> did not compile%n", branch + " e)");
        }
    }

    static void raise() throws BaseException {
        throw new DerivedException("event");
    }

    public static void main(String[] args) {
        for (String branch : new String[]
                {"BaseException", "DerivedException", "IndependentException", "RuntimeException", "Exception"})
            attempt(branch);

        System.out.println();
        try {
            raise();
        } catch (BaseException e) {
            System.out.println("BaseException branch caught, object's class : "
                    + e.getClass().getSimpleName());
        }
        try {
            raise();
        } catch (DerivedException e) {
            System.out.println("branch that runs with two branches          : DerivedException");
        } catch (BaseException e) {
            System.out.println("branch that runs with two branches          : BaseException");
        }
    }
}
```

```
catch (BaseException e)    -> compiled
catch (DerivedException e) -> compiled
catch (IndependentException e) -> did not compile
catch (RuntimeException e) -> compiled
catch (Exception e)        -> compiled

BaseException branch caught, object's class : DerivedException
branch that runs with two branches          : DerivedException
```

The upper table is a catch's **compile-time** face, and the one
rejected branch is the interesting one. `IndependentException` is a
checked exception and does not appear in the declaration of the call
inside the `try` block; the compiler does not even let that branch be
**written**. The decision here comes entirely from declared types: the
compiler works out, from the declarations, which checked exceptions can
come out of the block, and treats a branch outside that set as
**dead**. The `RuntimeException` branch passing is the other side of
the same rule — since an unchecked exception can come from anywhere, it
is never considered dead.

The lower lines are the same catch's **runtime** face, and this
lesson's boundary measurement. The throwing method declares
`BaseException`, but the class of the object it throws is
`DerivedException`. The `BaseException` branch catches this object, and
the caught object's class reads as `DerivedException`. Once the two
branches sit side by side, the branch that runs is `DerivedException` —
that is, the choice is made not from the declared type, but from the
**runtime type**.

Finding two opposite answers in the same structure is this course's
basic claim, in this topic's shape. A `catch` branch being **writable**
depends on the declared type; it **running** depends on the runtime
type. A programmer looking at the wrong side can pay for this in two
separate ways: writing a narrow branch against the upper type and
seeing it never run, or writing a narrow branch under a wide one and
seeing it not compile.

#### Summary

- The only thing a checked exception forces at compile time is a
  choice: declaring it or catching it. Only one of nine writings gets
  rejected; all three writings for the unchecked exception and the
  severe error are valid.
- There is no behavioral difference between a checked and an unchecked
  exception; the difference sits only in whether the compiler accepts
  a writing.
- The declaration does not enter the method's descriptor, it sits in a
  separate attribute; the two kinds' throwing code is the exact same
  five instructions.
- The virtual machine never checks the declaration: a checked exception
  thrown through a type parameter can come out of a method that
  declares nothing. A declaration is a compile-time contract.
- An overriding method can narrow its declaration, not widen it; a
  call looking at the same object as `Base` has to handle it, a call
  looking at it as `Derived` does not.
- Boundary measurement: the declared type decides which catch branch
  can be written, the runtime type decides which one runs. Two
  opposite answers sit together in the same structure.

#### Next Step

In this lesson, one exception got thrown, declared, and caught; every
measurement had a single event, and where it went got tracked. In a
real body, though, an exception is often not alone: if a resource got
opened, that resource has to be closed while the body is failing, and
the close can fail too. In that case, two exceptions show up at once,
and one can cover the other. The next lesson puts a hand-written close
next to a compiler-generated one, and counts how many exceptions get
lost, how many get kept, and in what order the closing happens.

### Try-with-Resources

Source: https://academia.sh/en/courses/java-object-model/try-with-resources

When the body and the close fail at the same time, the hand-written form fails to reach one of the two events, while try-with-resources reaches both. The measurement shows, together, the count of lost and kept exceptions, closing's reverse order, and the extra five instructions the compiler generates.

In the previous lesson, every measurement had a single event: an
exception got thrown, declared, and caught. In a real body, an event is
often not alone. If a resource got opened, that resource has to be
closed while the body is failing, and the close can fail too. In that
case, two exceptions show up at once, and both have to go up the same
path.

Releasing a resource with a context manager was built in the Python
Fundamentals course; that mechanism is not repeated here. The question
here is numeric: when two events show up, how many can be reached,
which one becomes unreachable, and in what order does closing happen?
This course's question changes shape in this lesson: the party
deciding is not a type, it is **who wrote the code** — a hand-written
close and a compiler-generated close give separate results for the
same body.

#### The Lost Exception and the Carried Exception

- **GE21** — The resource class gets defined within the lesson; every
  open and close gets written to a log, and the log gets cleared
  before every measurement.
- **GE22** — How many events each measurement produces is known from
  the setup and sits as the denominator in the table; reached count is
  counted from the escaped exception and the suppressed exceptions it
  carries.
- **GE23** — Only the message text gets printed from the exceptions;
  the stack trace and location do not.

```java
// Closing.java - hand-written closing next to try-with-resources
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Event extends Exception {
    Event(String m) { super(m); }
}

class Resource implements AutoCloseable {
    static final List<String> log = new ArrayList<>();
    private final String name;
    private final boolean failsOnClose;

    Resource(String name, boolean failsOnClose) {
        this.name = name;
        this.failsOnClose = failsOnClose;
        log.add("opened " + name);
    }

    @Override public void close() throws Event {
        log.add("closed " + name);
        if (failsOnClose) throw new Event("close " + name);
    }
}

interface Trial {
    void run() throws Event;
}

public class Closing {
    static void manual() throws Event {
        Resource r = new Resource("A", true);
        try {
            throw new Event("body");
        } finally {
            r.close();
        }
    }

    static void tryWithResources() throws Event {
        try (Resource r = new Resource("A", true)) {
            throw new Event("body");
        }
    }

    static void closeOnly() throws Event {
        try (Resource r = new Resource("A", true)) {
            Resource.log.add("body intact");
        }
    }

    static void twoClosing() throws Event {
        try (Resource a = new Resource("A", true); Resource b = new Resource("B", true)) {
            Resource.log.add("body intact");
        }
    }

    static void threeResources() throws Event {
        try (Resource a = new Resource("A", false);
             Resource b = new Resource("B", false);
             Resource c = new Resource("C", false)) {
            Resource.log.add("body intact");
        }
    }

    static void report(String label, int events, Trial t) {
        Resource.log.clear();
        String escaped = "-";
        int reached = 0;
        try {
            t.run();
        } catch (Event e) {
            escaped = e.getMessage();
            reached = 1 + e.getSuppressed().length;
            List<String> suppressed =
                    Arrays.stream(e.getSuppressed()).map(Throwable::getMessage).toList();
            if (!suppressed.isEmpty()) escaped += " + " + suppressed;
        }
        System.out.printf("%-16s %-26s %-9s %s%n", label, escaped, reached + " / " + events,
                Resource.log);
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-26s %-9s %s%n", "measurement", "escaped (+ suppressed)",
                "reached", "log");
        report("manual", 2, Closing::manual);
        report("try-with-resources", 2, Closing::tryWithResources);
        report("close only", 1, Closing::closeOnly);
        report("two closes", 2, Closing::twoClosing);
        report("three resources", 0, Closing::threeResources);
    }
}
```

```
measurement      escaped (+ suppressed)     reached   log
manual           close A                    1 / 2     [opened A, closed A]
try-with-resources body + [close A]           2 / 2     [opened A, closed A]
close only       close A                    1 / 1     [opened A, body intact, closed A]
two closes       close B + [close A]        2 / 2     [opened A, opened B, body intact, closed B, closed A]
three resources  -                          0 / 0     [opened A, opened B, opened C, body intact, closed C, closed B, closed A]
```

The first two rows are this lesson's measurement. Same body, same
resource, same two events: one exception in the body, one in the
close. In the hand-written form, the only exception reaching the
caller is the **close** exception; the body's exception stops nowhere
and gets **lost**. Reached count is one of two. In try-with-resources,
reached count is two of two: the escaped exception is the **body's**,
and the close exception gets carried as its **suppressed** exception.

The reason for the loss is in the **meaning of `finally`**. If an
exception comes out of `finally`, it **replaces** the exception already
on its way; two exceptions cannot travel up at once, and the
hand-written form of the language has no place to keep the second one.
Try-with-resources opens exactly that place: the body's exception stays
on the way, and the close's exception gets attached to it.

The measured cost of the loss is **which event the defect shows up
as**. In the hand-written form, the caller sees the message "close A";
the actual event that stopped the program, though, is in the body, and
that event leaves no trace anywhere. A resource that fails while
closing **covers** everything that failed before it.

#### Closing Order

The third, fourth, and fifth rows' logs write out closing's order. In
the three-resource measurement, opening order is `A, B, C`, closing
order is `C, B, A`. Resources get closed in the **reverse** order of
opening, and this is not arbitrary: a resource opened later may depend
on one opened earlier, never the other way around.

The fourth row shows the same rule together with two events. `A` and
`B` get opened, the body finishes intact, both fail while closing. The
exception reaching the caller is `B`'s close exception — that is, the
one closed **first**, the one opened **later**. `A`'s gets written as
its suppressed exception. Order decides which one comes out here too:
whichever exception is first on the way, the rest get attached to it.

In the fifth row, no resource fails while closing, and reached event
count is zero; the log still shows three opens and three closes.
Closing happens in every case, whether there is an exception or not.

#### The Declared Type Decides Eligibility as a Resource

- **GE24** — The three parts of the Scale core used in this lesson get
  written: compiling, a method's instruction count, and the names of
  the methods it calls.
- **GE25** — Across the six attempts, the types and bodies are fixed;
  only the type the resource gets declared with, the line in the
  body, and the enclosing method's declaration change.

```java
// Scale.java - shared measurement core: compiles a class file and reads it
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.InvokeInstruction;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Scale {
    static ClassModel compile(String source, String className) throws Exception {
        Path dir = Files.createTempDirectory("scale");
        Path file = dir.resolve(className + ".java");
        Files.writeString(file, source);
        PrintWriter silent = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(silent, silent, "-d", dir.toString(), file.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return ClassFile.of().parse(dir.resolve(className + ".class"));
    }

    static int instructionCount(String source, String className, String methodName) throws Exception {
        int count = 0;
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(methodName))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction) count++;
        return count;
    }

    static List<String> calls(String source, String className, String methodName) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(methodName))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof InvokeInstruction iv)
                        names.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
        return names;
    }

    static String shortName(String internalName) { return internalName.substring(internalName.lastIndexOf('/') + 1); }
}
```

```java
// Eligible.java - when can a name be a resource
public class Eligible {
    static final String TYPES = """
        class Event extends Exception { Event(String m) { super(m); } }
        class Closing implements AutoCloseable { @Override public void close() throws Event { } }
        class Silent implements AutoCloseable { @Override public void close() { } }
        class NotClosing { public void close() { } }
        """;

    static final String DECLARING = TYPES + "class C { static void y() throws Event { %s } }";
    static final String NOT_DECLARING = TYPES + "class C { static void y() { %s } }";

    static void attempt(String pattern, String label, String body) {
        try {
            Scale.compile(pattern.formatted(body), "C");
            System.out.printf("%-42s -> compiled%n", label);
        } catch (Exception e) {
            System.out.printf("%-42s -> did not compile%n", label);
        }
    }

    public static void main(String[] args) {
        System.out.println("enclosing method declares Event");
        attempt(DECLARING, "try (Closing k = new Closing())", "try (Closing k = new Closing()) { }");
        attempt(DECLARING, "try (Object k = new Closing())", "try (Object k = new Closing()) { }");
        attempt(DECLARING, "try (NotClosing k = new NotClosing())",
                "try (NotClosing k = new NotClosing()) { }");
        attempt(DECLARING, "reassigned inside body k = new Closing()",
                "try (Closing k = new Closing()) { k = new Closing(); }");
        System.out.println();
        System.out.println("enclosing method declares nothing");
        attempt(NOT_DECLARING, "try (Closing k = new Closing())", "try (Closing k = new Closing()) { }");
        attempt(NOT_DECLARING, "try (Silent k = new Silent())", "try (Silent k = new Silent()) { }");
    }
}
```

```
enclosing method declares Event
try (Closing k = new Closing())            -> compiled
try (Object k = new Closing())             -> did not compile
try (NotClosing k = new NotClosing())      -> did not compile
reassigned inside body k = new Closing()   -> did not compile

enclosing method declares nothing
try (Closing k = new Closing())            -> did not compile
try (Silent k = new Silent())              -> compiled
```

The second row ties this lesson to the course's axis. The same object
— runtime type `Closing`, that is, a closable object — **cannot be a
resource** once named as `Object`. Even though the object is closable,
the compiler does not generate the close call, because it looks at the
**declared type** to decide whether a call to generate exists at all.
Try-with-resources's entire mechanism gets built at compile time, and
the object's actual class never gets asked.

The third row is the same rule's lower bound: a class that has a
method named `close` but does not take on the closability
**contract** cannot be a resource. The name is not enough; what is
needed is the declared type carrying that contract.

The fourth row shows the resource variable cannot get rebound inside
the body. The reasoning for this is the generated code itself: the
compiler generates the close call for the object bound at the
**start**, and if the variable could be rebound, the object closed and
the object opened would come apart.

The last two rows are the previous lesson's narrowing rule's
counterpart here. When the `close` method declares a checked
exception, the try-with-resources block carries that exception to the
caller, and the enclosing method has to make a choice. The same block
produces no requirement when `close` declares nothing. The cost
try-with-resources writes to the caller matches the resource's close
declaration **exactly**.

#### The Code the Compiler Generates

- **GE26** — The measured source gets written within the lesson; both
  methods' bodies do the same work, only the way the close gets
  written changes.

```java
// Generated.java - what try-with-resources produces in the class file
public class Generated {
    static final String SOURCE = """
        class Event extends Exception { Event(String m) { super(m); } }
        class Resource implements AutoCloseable {
            void work() { }
            @Override public void close() throws Event { }
        }
        class C {
            static void manual() throws Event {
                Resource r = new Resource();
                try { r.work(); } finally { r.close(); }
            }
            static void tryWithResources() throws Event {
                try (Resource r = new Resource()) { r.work(); }
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        for (String y : new String[] {"manual", "tryWithResources"})
            System.out.printf("%-18s instructions %2d  calls %s%n", y,
                    Scale.instructionCount(SOURCE, "C", y), Scale.calls(SOURCE, "C", y));
    }
}
```

```
manual             instructions 15  calls [Resource.<init>, Resource.work, Resource.close, Resource.close]
tryWithResources   instructions 20  calls [Resource.<init>, Resource.work, Resource.close, Resource.close, Throwable.addSuppressed]
```

The form that is two lines shorter in the source is **five
instructions longer** in the class file: the hand-written version is
15, the try-with-resources version 20. Try-with-resources is a
**shortening**, but what it shortens is the written text; the code it
generates does extra work.

Two `close` calls show up in the call lists: one for the path that
runs when the body finishes cleanly, one for the path that runs when
the body fails. Both of these exist in the hand-written form too,
because the compiler places the `finally` block on both paths. What
makes the difference is the **fifth** call, showing up only in
try-with-resources: the **suppression** call. The extra five
instructions belong to this too — holding the exception escaping the
body, trying the close on a separate branch, and attaching the second
exception to the first.

What the measurement says: a suppressed exception is not something
that happens on its own at the language's runtime. There is a call,
inside the code closing the resource, that asks for suppression, and
the compiler put that call there; someone writing the same call by
hand would get the same result. What try-with-resources earns is not
a new capability, it is that code getting written **every time, and
in the same way**.

#### A Closing Exception Does Not Suppress the Body's

This lesson's boundary measurement sits in comparing the second and
third rows, and shows that suppression's direction runs one way.

In the second row, both the body and the close fail, and the escaped
exception is the **body's**; the close's gets carried as suppressed.
The direction never reverses: an exception failing during close cannot
suppress the body's, because the body's exception is already on the
way and gives its place to no one.

The third row draws this rule's boundary. When the body finishes
intact, there is nothing to suppress; the close exception comes out
**directly**, and its suppressed list is empty. Suppression, that is,
is not a hiding mechanism, it is only a place carrying a **second**
event. A defect in closing loses nothing of its visibility when it is
alone.

The rule these two rows say together: try-with-resources destroys no
event and changes no event's priority. The exception lost in the
hand-written form was lost not because the priority order was wrong,
but because there was **no place to put a second event**.

#### Summary

- When the body and the close fail at the same time, only one of two
  events gets reached in the hand-written form; both get reached with
  try-with-resources.
- In the hand-written form, an exception coming out of `finally`
  replaces the one on its way, and no trace of the body's event
  remains; the caller sees the close event, not the event that
  stopped the program.
- Resources get closed in the reverse order of opening; when more
  than one close fails, the first one closed's exception comes out,
  and the rest get written to it as suppressed.
- The declared type decides whether a name can be a resource: a
  closable object does not get closed once named as `Object`, and the
  block writes to the caller only as much cost as the close
  declaration carries.
- Try-with-resources is five instructions longer than the hand-written
  form in the class file; what shortens is the written text, not the
  generated code.
- Boundary measurement: suppression's direction runs one way. A
  closing exception does not suppress the body's, only the reverse
  happens; when the body finishes intact, the closing exception comes
  out directly and alone.

#### Next Step

Across three lessons, an exception was always a given: it got thrown,
declared, caught, suppressed. None of them asked whether an event
**should be reported as an exception at all**. Yet the same work can
be written with three separate contracts: a checked exception, an
unchecked exception, or no exception at all, returning the result
through a type. This course's last lesson builds all three around the
same work, counts how many decision points the compiler forces on the
calling side, and measures where a defect shows up once a contract
breaks.

### Exception Design

Source: https://academia.sh/en/courses/java-object-model/exception-design

The same work gets built with three contracts: a checked exception, an unchecked exception, and a result type. The measurement shows the count of decision points the compiler forces on the calling side, and what a careless caller gets from an empty store; none of the three contracts eliminates the defect, they only move where it shows up.

The previous four lessons took the exception as a given: it got
thrown, declared, caught, suppressed. None of them asked whether an
event should be reported as an exception at all. The same work can be
written with three separate contracts — a checked exception, an
unchecked exception, or no exception at all, returning the result
through a type.

This lesson builds all three around the same work: getting an item
from a store that might be empty. This course's question turns into a
design question here, but the measure stays the same. Whoever chooses
the contract chooses what the caller **has to** do; and the party doing
the forcing is always the compiler.

#### The Decision Point the Compiler Forces

- **GE27** — The three contracts get measured on the same store, with
  the same three-layer calling chain: `inner` does the work, `middle`
  calls it, `outer` calls `middle`.
- **GE28** — The eight attempts are the set of which of the three
  points get the declaration. Since there is no declaration to place
  for the result type, that row's subset count gets left blank.

```java
// Scale.java - shared measurement core: compiles the given source
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Scale {
    static ClassModel compile(String source, String className) throws Exception {
        Path dir = Files.createTempDirectory("scale");
        Path file = dir.resolve(className + ".java");
        Files.writeString(file, source);
        PrintWriter silent = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(silent, silent, "-d", dir.toString(), file.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        return ClassFile.of().parse(dir.resolve(className + ".class"));
    }
}
```

```java
// Force.java - how many points three contracts force in the calling chain
public class Force {
    static final String TYPES = """
        class Event extends Exception { Event(String m) { super(m); } }
        class SilentEvent extends RuntimeException { SilentEvent(String m) { super(m); } }
        record Result(String value, String error) { }
        class Store {
            String checked() throws Event { throw new Event("empty"); }
            String unchecked() { throw new SilentEvent("empty"); }
            Result result() { return new Result(null, "empty"); }
        }
        """;

    static String chain(String type, String call, String[] declarations) {
        return TYPES + "class C {\n"
                + "    static " + type + " inner(Store d) " + declarations[0]
                + " { return d." + call + "(); }\n"
                + "    static " + type + " middle(Store d) " + declarations[1] + " { return inner(d); }\n"
                + "    static " + type + " outer(Store d) " + declarations[2] + " { return middle(d); }\n}\n";
    }

    static boolean compiles(String source) {
        try {
            Scale.compile(source, "C");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    static void measure(String label, String type, String call, String declaration) {
        boolean untouched = compiles(chain(type, call, new String[] {"", "", ""}));
        int minimum = -1, passed = 0;
        for (int m = 0; m < 8; m++) {
            String[] declarations = new String[3];
            int points = 0;
            for (int i = 0; i < 3; i++) {
                boolean present = (m >> i & 1) == 1;
                declarations[i] = present ? declaration : "";
                if (present) points++;
            }
            if (compiles(chain(type, call, declarations))) {
                passed++;
                if (minimum < 0 || points < minimum) minimum = points;
            }
        }
        System.out.printf("%-16s %-20s %-10d %s%n", label,
                untouched ? "compiled" : "did not compile", minimum,
                declaration.isEmpty() ? "-" : passed + " / 8");
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-20s %-10s %s%n",
                "contract", "untouched chain", "min points", "compiled subset");
        measure("checked", "String", "checked", "throws Event");
        measure("unchecked", "String", "unchecked", "throws SilentEvent");
        measure("result type", "Result", "result", "");
    }
}
```

```
contract         untouched chain      min points compiled subset
checked          did not compile      3          1 / 8
unchecked        compiled             0          8 / 8
result type      compiled             0          -
```

The first row counts the cost the checked exception writes to the
caller. The chain untouched anywhere does not compile, and the
smallest declaration set that makes the chain compile has size
**three** — that is, **every layer**. Only one of eight subsets
compiles: the set where all three declare. The requirement cannot be
stopped at a layer in between, because if one layer does not declare,
the layer calling it never learns the exception exists at all.

The second and third rows give **zero**. For the unchecked exception,
the chain compiles with no touch at all; all eight of eight subsets
are valid, that is, declaration is entirely optional. For the result
type, there is not even a declaration to place.

A measured comparison follows from this. A checked exception writes
the error path's existence into **every layer** of the chain; this is
documentation, and it gets enforced by the compiler. In the other two
contracts, the error path is invisible in any of the intermediate
layers. What gets forced, though, is only **declaring**, not
**handling** — and the next measurement shows what that means.

#### Same Work, Three Contracts

- **GE29** — Two callers get written for each contract: the shortest
  writing the compiler accepts, and a writing that genuinely handles
  the error path. Both run against the same empty store and the same
  full store.
- **GE30** — From the caller's outcome, only the returned value or
  the class name of the escaped exception gets printed.

```java
// Contract.java - same work, three contracts, a careless and a careful caller
import java.util.function.Function;

class Event extends Exception {
    Event(String m) { super(m); }
}

class SilentEvent extends RuntimeException {
    SilentEvent(String m) { super(m); }
}

record Result(String value, String error) { }

class Store {
    private final String item;

    Store(String item) { this.item = item; }

    String checked() throws Event {
        if (item == null) throw new Event("empty store");
        return item;
    }

    String unchecked() {
        if (item == null) throw new SilentEvent("empty store");
        return item;
    }

    Result result() {
        return item == null ? new Result(null, "empty store") : new Result(item, null);
    }
}

public class Contract {
    static String carelessChecked(Store d) {
        try {
            return d.checked();
        } catch (Event e) {
            return "";
        }
    }

    static String carefulChecked(Store d) {
        try {
            return d.checked();
        } catch (Event e) {
            return "fallback (" + e.getMessage() + ")";
        }
    }

    static String carefulUnchecked(Store d) {
        try {
            return d.unchecked();
        } catch (SilentEvent e) {
            return "fallback (" + e.getMessage() + ")";
        }
    }

    static String carefulResult(Store d) {
        Result s = d.result();
        return s.error() == null ? s.value() : "fallback (" + s.error() + ")";
    }

    static String run(Function<Store, String> f, Store d) {
        try {
            return "value \"" + f.apply(d) + "\"";
        } catch (RuntimeException e) {
            return "exception " + e.getClass().getSimpleName();
        }
    }

    static void row(String label, Function<Store, String> careless, Function<Store, String> careful) {
        Store empty = new Store(null), full = new Store("north");
        System.out.printf("%-16s %-26s %-26s %s%n", label,
                run(careless, empty), run(careful, empty), run(careless, full));
    }

    public static void main(String[] args) {
        System.out.printf("%-16s %-26s %-26s %s%n",
                "contract", "careless / empty store", "careful / empty store", "careless / full store");
        row("checked", Contract::carelessChecked, Contract::carefulChecked);
        row("unchecked", d -> d.unchecked(), Contract::carefulUnchecked);
        row("result type", d -> String.valueOf(d.result().value()), Contract::carefulResult);
    }
}
```

```
contract         careless / empty store     careful / empty store      careless / full store
checked          value ""                   value "fallback (empty store)" value "north"
unchecked        exception SilentEvent      value "fallback (empty store)" value "north"
result type      value "null"               value "fallback (empty store)" value "north"
```

The first column gives three separate defect shapes, and all three
come out of the same empty store.

In the checked contract, the careless caller returns an **empty
string**. The compiler forced a catch to be written, but never asked
what got put inside it; an empty body satisfies the requirement
completely. A checked exception's guarantee is the error path being
**seen**, not being **handled**.

In the unchecked contract, the careless caller **fails at runtime**;
the defect takes its loudest shape here. In the result type, it gets
stopped neither at compile time nor does it fail at runtime; it
returns the text `null` **as a value**. This is the quietest of the
three shapes, because the error path has become ordinary data, not an
exception.

The second column writes down the three contracts' common point: once
the error path genuinely gets handled, all three give the **same
result**. The third column shows where the distinction lives — once
the store is full, the three contracts are **indistinguishable**.
Choosing a contract changes nothing on the correct path; it is
entirely a decision about the error path.

#### The Defect Does Not Disappear, It Moves

This lesson's boundary measurement is reading the two tables together,
and it declares none of the three contracts superior.

The defect is **singular**: an item got requested from an empty
store. The three contracts do not remove this defect, they only
choose **where it shows up**. A checked exception carries it into the
caller's **compilation** — all three layers have to write the error
path, but what they write is not required to be correct. An unchecked
exception carries it into **runtime**; no layer of the source gets
stained, but which call can fail cannot be seen by looking at the
source. A result type carries it into the **data layer**; it never
fails anywhere, but the wrong value keeps flowing.

In all three places, the one paying the price is a different party: in
the first, whoever **writes**; in the second, whoever **runs**; in the
third, whoever **reads the result**. The measure that has run through
this course since its first lesson takes its final shape here — where
a decision gets made decides where the defect shows up, and no choice
eliminates the defect.

#### Summary

- A checked exception makes declaration mandatory in every layer of
  the call chain: only one of eight subsets compiles, and the
  smallest declaration set's size is three.
- An unchecked exception and a result type force no point at all; the
  chain compiles untouched, and the error path is invisible in the
  intermediate layers' source.
- What gets forced is declaring, not handling: a careless caller
  satisfies the requirement by writing an empty catch, and returns an
  empty value.
- The same empty store produces three separate defect shapes: an
  empty value, a runtime exception, and a `null` turned into data.
  Once the error path gets handled, the three contracts give the same
  result; once the store is full, they cannot even be told apart.
- Boundary measurement: none of the three contracts eliminates the
  defect. The defect is singular, and the contract only chooses where
  it shows up — at compilation, at throwing, or at reading.

#### Course Wrap-Up

This course opened with a single question: when a name carries two
types, which one decides? Thirteen lessons asked this question of
thirteen separate language decisions, and got the answer every time
from an executed measurement. Gathered together, a single rule
emerges: **the behavior called looks at the runtime type, the name
chosen looks at the declared type** — and the boundary between the two
is invisible in the syntax.

| Lesson | Measured decision | Deciding party | Boundary measurement |
|---|---|---|---|
| Class, Object, and Constructor | overridden method called from the constructor chain | runtime type | no trap is born in a private or final method, all three compile to the same instruction |
| Access Modifiers | visibility: 10 of 16 pairs compile | declared type (compile time) | the member stays put at runtime, only a flag carries the distinction |
| Inheritance and Overriding | four of eight and four of eight decisions | four runtime type, four declared type | overriding is not born when the signature does not match, the deciding party switches |
| Abstract Classes and Interfaces | eight setups of the same capability, three stop compilation | the compiler, without asking which side | a capability carrying state cannot be built with an interface |
| Static Members and Initialization Blocks | static method selection and class-level state | declared type | the distinction cannot be observed once the two sides coincide |
| The final Keyword | reassignment, overriding, and widening bounds | declared type (compile time) | final does not freeze the referenced object |
| Inner and Local Classes | generated class files and hidden fields | the compiler, without it being written in the source | a static nested class carries no hidden field |
| Records and Enumerations | which side answers for the generated members | runtime type | a record's protection is shallow, an array component changes from outside |
| Generic Types | what the type parameter leaves behind at runtime | declared type, entirely | the signature attribute stays: what gets erased is not the information, it is the check |
| Bounded Wildcards | four container forms × two operations, four of eight | declared type, entirely | all four can be read as Object, all four can have null written to them |
| Exception Hierarchy | what the checked distinction forces: one of nine writings gets rejected | declared type for writing, runtime type for the branch | the virtual machine never checks the declaration |
| Try-with-Resources | closing order and the suppressed exception | the compiler, looking at the declared type | suppression's direction runs one way, closing never suppresses the body's |
| Exception Design | the decision point three contracts force: 3, 0, 0 | declared type (compile time) | none of the three contracts eliminates the defect, they move it |

The table's third column is this course's method; the fourth is where
each lesson bounds its own thesis. Read together, the course's real
claim shows through: **the difference is invisible in the syntax.**
`d.name()` and `d.label` get written with a two-character difference
and get answered by separate parties; `Store<Base>` and
`Store<String>` get written separately and become a single class; a
`throws` declaration sits in the source and never gets checked at
runtime.

A second reading comes from generics, and is the shared lesson of the
last five lessons. When a check gets moved to compile time, it does
not disappear, it only **relocates**; and once a path opens that slips
past that check, the defect shows up not where it was produced, but
much later, where it gets **read**. A value written through the raw
type falls at the reading line; without try-with-resources written, an
exception gets lost at the closing line; when the wrong contract gets
chosen, the defect turns into data.

In this course, classes always worked **alone**: a store, a
superclass, a subclass. The next course, **Standard Library and
Streams**, puts the same types inside collections and streams. There,
the type parameter travels not in a single store, but through
operations **chained** to one another; and the question "which side
decided" becomes askable again, because every link in a stream carries
its own declared type.

