---
title: 'Java Fundamentals'
source: 'https://academia.sh/en/courses/java-fundamentals'
language: en
updated: '2026-08-17T04:41:18+00:00'
license: 'CC BY-SA 4.0'
lessons: 10
outcomes:
    - 'Explains the compilation and execution chain of a Java program'
    - 'Writes programs with primitive types, control flow, and arrays'
    - 'Uses value and reference semantics correctly'
---

# Java Fundamentals

In Java, what is written is not what is run: the course counts how many steps each syntax form turns into in the class file and which step the compiler adds that is not in the source; it builds the language's own machinery with that measure, from the execution chain to value and reference passing.

## Language and Runtime

Measuring the chain from source to class file and virtual machine, the steps a program passes through from its entry point to termination, and the trace a package and an import leave in name resolution.

### Source, Bytecode, and the Virtual Machine

Source: https://academia.sh/en/courses/java-fundamentals/source-bytecode-and-virtual-machine

The source written in Java is not the thing that runs: eight of twelve methods carry a step in the class file that is absent from the source, 13 added steps counted in total; a concatenation sitting in the source, by contrast, is entirely absent from the class file.

The Python curriculum closed with five courses, and its closing course defined a project's
number with a single question: how many distinct outcomes does the same input give. Something
was shared across everything measured through that curriculum — the source written stayed
close to what the interpreter saw. When asked which method got called under a syntax form, the
answer was searched for **at runtime**, because the distance between the source text and the
thing running was short.

In Java, that distance is not short. Between the source and the thing that runs sits a
**compiler** and the **class file** it produces; the **Java virtual machine** reads not the
source but that file. The difference in between is not a difference in form: the class file
holds things not written in the source. This lesson's question, and the whole course's, comes
from here — the line written turns into **how many steps, and which ones**, in the class file?

#### The Chain and This Lesson's Share

The How Computers Work course established the compiler, interpreter, and virtual machine
trio, the concept of **bytecode**, and **just-in-time compilation**; it also covered
compilation stages in a separate lesson. **These are not repeated here.** What was established
there was the **concept**; what is measured here is the **file** a compiler actually produces
for this source.

The chain holds three stops. Source text is what the human writes. The compiler turns it into
a class file; this file carries bytecode, that is, a sequence of instructions written in the
virtual machine's instruction set. The virtual machine loads the file and executes the
instructions. Only the first of the two passes is this lesson's subject: **the difference
between the source and the class file.**

The difference being measurable comes from a convenience. The class file's format is defined,
and the standard library carries both an interface reading that file and a compiler callable
from within a program. No external tool is needed for the measurement: we hand over a text,
compile it, read the file that comes out. Since we write the source ourselves, we know what we
wrote; whatever extra we find in the file is what the compiler put there.

#### The Measurement Core

Every measurement in this course passes through a single helper class. `Gauge` compiles a
given source text, reads the class file it produces, and returns two things for every method:
**how many instructions** were produced, and **which step not written in the source** was
added. An added step is one of five kinds — a call, a dynamic call, an object or array
creation, a type check, a numeric conversion.

The measurement's assumptions:

- **LR1** — The oracle is the rig itself: since we wrote the source ourselves, we know what we
  wrote, so whatever extra is found in the class file is not an inference, it is a dump.
- **LR2** — The compiler and the class-file reader are part of the standard library and are
  called from within the program; no external tool is used.
- **LR3** — The counted unit is the **instruction**. The numbers come from one compiler run and
  are deterministic within that run; the lesson's thesis does not rest on a single instruction
  count, it rests on the **direction** between the numbers.
- **LR4** — The measurement reads no environment-dependent data: duration, memory address,
  identity, and path are not written.
- **LR5** — The measured source carries twelve methods, and these methods cover the syntax
  forms the course will pay off lesson by lesson; it is not the language's whole syntax.

```java
// Gauge.java — the measurement core: reads what the compiler adds to 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 Method(String name, int instructions, List<String> added) {}

    static ClassModel 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 ClassFile.of().parse(d.resolve(className + ".class"));
    }

    static List<Method> read(String source, String className) throws Exception {
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof InvokeInstruction iv)
                    added.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    added.add("dynamic:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    added.add("new " + shortName(n.className().asInternalName()));
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    added.add("new array " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    added.add("new array " + shortName(a.componentType().asInternalName()));
                else if (e instanceof TypeCheckInstruction t && t.opcode() == Opcode.CHECKCAST)
                    added.add("type check " + shortName(t.type().asInternalName()));
                else if (e instanceof ConvertInstruction c)
                    added.add("convert " + c.opcode().name().toLowerCase(Locale.ROOT));
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, added));
        }
        return methods;
    }

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

The measured source also sits in a separate file, because this lesson will read it twice.

```java
// Source.java — the measured source text: twelve methods, twelve syntax forms
class Source {
    static final String SAMPLE = """
        import java.util.List;
        class Sample {
            static int add(int a, int b) { return a + b; }
            static Object box(int n) { Integer k = n; return k; }
            static int unbox(Integer k) { int n = k; return n; }
            static String concat(String a, int n) { return a + n; }
            static String constantConcat() { return "sim" + "ple"; }
            static String loopConcat(String[] d) {
                String s = "";
                for (String x : d) s += x;
                return s;
            }
            static int arraySum(int[] d) { int t = 0; for (int x : d) t += x; return t; }
            static int listSum(List<Integer> l) { int t = 0; for (int x : l) t += x; return t; }
            static long widen(byte b, long u) { return b + u; }
            static byte narrow(byte b) { b += 300; return b; }
            static int variadic(int... numbers) { return numbers.length; }
            static int variadicCall() { return variadic(1, 2, 3); }
        }
        """;
}
```

#### Method by Method: How Many Instructions, Which Step

```java
// Main.java — instruction count and step not in source, for every method
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s  %s%n", "method", "instrs", "step not in source");
        int written = 0, withAdded = 0, total = 0;
        for (Gauge.Method y : Gauge.read(Source.SAMPLE, "Sample")) {
            System.out.printf("%-18s %6d  %s%n", y.name(), y.instructions(),
                    y.added().isEmpty() ? "-" : String.join(", ", y.added()));
            if (y.name().equals("<init>")) continue;
            written++;
            if (!y.added().isEmpty()) withAdded++;
            total += y.added().size();
        }
        System.out.println("written methods: " + written
                + " | carrying an added step: " + withAdded
                + " | total added steps: " + total);
    }
}
```

```
method             instrs  step not in source
<init>                  3  Object.<init>
add                     4  -
box                     5  Integer.valueOf
unbox                   5  Integer.intValue
concat                  4  dynamic:makeConcatWithConstants
constantConcat          2  -
loopConcat             24  dynamic:makeConcatWithConstants
arraySum               24  -
listSum                20  List.iterator, Iterator.hasNext, Iterator.next, type check Integer, Integer.intValue
widen                   5  convert i2l
narrow                  7  convert i2b
variadic                3  -
variadicCall           16  new array int, Sample.variadic
written methods: 12 | carrying an added step: 8 | total added steps: 13
```

#### Reading the Numbers

**Eight** of the twelve written methods carry a step in the class file not present in the
source; **13 added steps** in total. This is the measure the course will carry: **an added
step is the rule, not the exception.** In every row of the table, the method we wrote sits on
the left, what the compiler put there sits on the right.

Reading the rows one by one shows where the addition comes from. `Integer k = n` is an
assignment in the source, and an `Integer.valueOf` call in the class file. `a + n` is written
like an operator, and is a **dynamic call** in the class file. On the `b + u` line, a
conversion instruction is added so a `byte` value can be added to a `long`. `variadic(1, 2, 3)`
stands like a three-argument call; in the class file, an array is created first, then a
single-argument call is made — this is why a three-instruction method's call costs **16
instructions**.

The **four** methods carrying no added step give just as much information: `add`,
`constantConcat`, `arraySum`, and `variadic`. What they share is this — none of them asks
another party a question at runtime. Adding two integers, reading an array's element, asking
an array's length: all of these are met by the virtual machine's direct instructions, and no
call steps in between. This is where the added step's condition is read: the compiler puts a
step in between when it **cannot find** a match for the written form in the instruction set.

This fourth method also shows where an added step lands. `variadic` is three instructions and
carries no step; its **call**, though, costs 16 instructions and carries an array creation. The
cost of a method taking a variable number of arguments does not sit in its definition, it sits
**where it is called**. Looking only at a form's definition, when searching for its cost, is
misleading.

The table also has a method **never written in the source** at all: `<init>`, a
three-instruction constructor. No constructor is written for the `Sample` class in the source;
the class file has one, and its only job is to call the base class's constructor. The next
lesson measures when this line disappears.

The sharpest reading comes from comparing two rows. `arraySum` and `listSum` carry the
**same** loop form in the source — both are the enhanced `for` loop. On an array, this loop
produces **24 instructions** and **zero** added steps; on a list, **20 instructions** and
**five** added steps. That is, **fewer instructions, more added steps**: instruction count and
added-step count do not move in the same direction. On the array, the virtual machine's direct
instructions are enough; on the list, an iterator chain is set up — `iterator`, `hasNext`,
`next`, a type check, and an `intValue`. The two measurements say separate things, and one
measurement cannot be compared to another without saying what it counts.

The same observation can be built the other way too. `loopConcat` and `arraySum` carry
**equal** instruction counts — both 24 — but one carries a dynamic call, the other carries no
step at all. Equal instruction count does not mean equal work. Counting instructions is one
measure; counting added steps is another, and neither substitutes for the other.

#### A Bounding Measurement: What Sits in Source, Missing from the Class File

Every row up to here showed an **addition**. The claim's boundary sits in the same table too:
`constantConcat`'s source writes a concatenation, and its class file carries **2 instructions**
and **zero** added steps. Given the same `+` operator turns into a dynamic call in `concat`,
what happened here? The class file's instruction names give the answer.

```java
// Trace.java — is the concatenation sitting in the source present in the class file
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.util.*;

public class Trace {
    static List<String> opcodes(ClassModel cm, String method) {
        List<String> a = new ArrayList<>();
        for (MethodModel m : cm.methods())
            if (m.methodName().equalsString(method) && m.code().isPresent())
                for (CodeElement e : m.code().get())
                    if (e instanceof Instruction i) a.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return a;
    }

    public static void main(String[] args) throws Exception {
        ClassModel cm = Gauge.compile(Source.SAMPLE, "Sample");
        System.out.println("constantConcat -> " + opcodes(cm, "constantConcat"));
        System.out.println("concat         -> " + opcodes(cm, "concat"));
        Set<String> strings = new HashSet<>();
        for (PoolEntry pe : cm.constantPool())
            if (pe instanceof StringEntry se) strings.add(se.stringValue());
        for (String s : List.of("simple", "sim", "ple"))
            System.out.printf("class file contains string literal \"%s\": %s%n", s, strings.contains(s));
    }
}
```

```
constantConcat -> [ldc, areturn]
concat         -> [aload_0, iload_1, invokedynamic, areturn]
class file contains string literal "simple": true
class file contains string literal "sim": false
class file contains string literal "ple": false
```

`constantConcat` consists of two instructions: load a constant, return. There is no
concatenation in there. Furthermore, the concatenation's **operands** are not there either: the
class file carries the string `"simple"`; `"sim"` and `"ple"` are not found in it. Two
constants have been folded into a single constant during compilation — this is **constant
folding**, which the How Computers Work course established as a concept, and here it appears
not as a concept but as a measured absence.

This is where the course's second claim is born: **an added step is not always an addition;
sometimes it is a subtraction.** The same `+` operator gives two separate results in two
methods — zero steps in one, a dynamic call in the other. What creates the distinction is not
the operator itself, it is **whether its operands are known at compile time**. Looking at the
written form cannot tell us what will happen in the class file.

#### Which Machine Was the Translation Written For

The instruction names say a second thing too. `aload_0` and `iload_1` refer to a **local
variable slot** by number, not a processor register. `ldc` pushes a value onto the **stack**,
`areturn` takes one off the stack and returns. This instruction set belongs to a stack machine,
and nowhere in it does a real hardware name appear.

The same holds for names. In the table, the `listSum` row writes `List.iterator` and
`Iterator.hasNext`: the class file carries the method it will call not by address, but **by
name**. What that name corresponds to in code is resolved not at compile time, but while the
file is being loaded.

This is portability's measure: the compiler wrote not to a machine with hardware, but to **a
machine with a specification**. The class file is not translated to a real processor; that
translation is taken on by the virtual machine itself, while running — this is the job the How
Computers Work course established under the heading of just-in-time compilation. What this
course measures is not that second pass, it is the first.

#### Summary

- In Java, a compiler and a class file sit between the source and the thing that runs; the
  virtual machine reads not the source, but the bytecode in the class file.
- The measurement core `Gauge` compiles a source text and, by reading the class file, dumps
  the instruction count and the steps not written in the source for every method.
- **Eight** of the twelve written methods carry an added step, **13 added steps** are counted
  in total; there is even a three-instruction constructor never written in the source at all.
- The same enhanced `for` loop produces **24 instructions** and **zero** added steps on an
  array, **20 instructions** and **five** added steps on a list: instruction count and added
  steps do not move in the same direction.
- A concatenation sitting in the source is entirely absent from the class file; two constants
  fold into one, and the operands are not found in the file. An added step is sometimes a
  subtraction.
- The class file's instructions refer to a slot number and a stack operation, and carry the
  method they will call by name: what got translated was written for a machine with a
  specification, not a machine with hardware.

#### Next Step

This lesson measured **what** the class file is, but never looked at **when** the instructions
inside it run. The table held a constructor never written in the source, and when it gets
called was never said; where a class's own setup work sits was never asked either. The next
lesson measures the program's life cycle: which signature does the virtual machine count as
the **entry point**, in what order do the static initializer and the constructor run, and how
many members does a class with two names written in the source carry on its class-file
surface?

### Program Life Cycle

Source: https://academia.sh/en/courses/java-fundamentals/program-life-cycle

The virtual machine counts as the entry point only the single signature satisfying four conditions at once; a class's static initializer runs not at the program's start but the moment the class is first used, and only once; an enum with two names written in the source carries three fields and five methods in the class file.

The previous lesson measured **what** a class file is: eight of the twelve methods written in
the source carried a step there absent from the source, and the table also held a
three-instruction constructor never written in the source at all. When that constructor gets
called was never said; where a class's own setup work sits was never asked either.

This lesson measures that gap. A class file sitting on disk does nothing on its own; the
virtual machine loads it, prepares it, and runs the instructions inside it in an order. There
are three questions: which signature does the virtual machine count as the **entry point**, in
what order does a class's setup work run against an instance's setup work, and which members
not written in the source join that order?

#### The Measurement Core

The previous lesson's `Gauge` core counted instructions. What is counted in this lesson is not
instructions, it is **members**: the fields and methods in the class file, every method's
signature, whether it is public and static, its return type. The core does the same job — it
compiles and reads the class file — but does not carry the instruction analysis this lesson
does not use.

- **LR6** — The entry-point measurement is read from the class file itself: the method's name,
  modifiers, return type, and parameter list. The measurement does not invoke a launcher; it
  tests the language's stated four conditions against real class files.
- **LR7** — In the order measurement, the oracle is the object's own record: every
  initialization step writes its name to a shared list when it runs, so the order is not an
  inference, it is a dump.
- **LR8** — The record list is printed once, at the program's end; the printing itself does not
  mix into the measured order.
- **LR9** — The member dump counts **all** members in the class file; those written in the
  source are on the list too. An added member is read by comparing the list against the
  source.
- **LR10** — The measurement reads no environment-dependent data: duration, memory address,
  identity, and path are not written; the only things counted are member count and run order.

```java
// Gauge.java — class file member dump: fields, method signatures and modifiers
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    record Member(String name, String signature, boolean isPublic, boolean isStatic, String returns) {}
    record ClassInfo(List<String> fields, List<Member> methods) {}

    static ClassInfo read(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"));
        List<String> fields = new ArrayList<>();
        for (FieldModel fm : cm.fields()) fields.add(fm.fieldName().stringValue());
        List<Member> methods = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            MethodTypeDesc t = m.methodTypeSymbol();
            List<String> p = new ArrayList<>();
            for (var a : t.parameterList()) p.add(a.displayName());
            methods.add(new Member(m.methodName().stringValue(), "(" + String.join(", ", p) + ")",
                    m.flags().has(AccessFlag.PUBLIC), m.flags().has(AccessFlag.STATIC),
                    t.returnType().displayName()));
        }
        return new ClassInfo(fields, methods);
    }

    static List<String> names(ClassInfo s) {
        List<String> a = new ArrayList<>();
        for (Member u : s.methods()) a.add(u.name() + u.signature());
        return a;
    }
}
```

#### The Entry Point: The Signature Satisfying Four Conditions at Once

Where a program starts, in Java, is not a file's first line. The launcher is given a **class**
name; that class gets loaded and a particular method is searched for inside it. What is
searched for is not a name, it is a **signature**: the method's name has to be `main`, it has
to be public, it has to be static, it has to return no value, and its single parameter has to
be a string array. If all four conditions are not met at once, that method is not the entry
point.

The measurement below compiles five separate sources and tests the `main` method in each
one's class file against these conditions. All five sources are flawless, and all five compile
without a hitch — the split does not surface at compile time, it surfaces after loading.

```java
// Main.java — the signature the virtual machine counts as the entry point
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Map<String, String> candidate = new LinkedHashMap<>();
        candidate.put("Correct",    "class Correct { public static void main(String[] a) { } }");
        candidate.put("NotStatic",  "class NotStatic { public void main(String[] a) { } }");
        candidate.put("NotPublic",  "class NotPublic { static void main(String[] a) { } }");
        candidate.put("NoArray",    "class NoArray { public static void main(String a) { } }");
        candidate.put("Returning",  "class Returning { public static int main(String[] a) { return 0; } }");
        System.out.printf("%-10s %-11s %-6s %-6s %-6s %s%n",
                "class", "sig", "public", "static", "return", "entry point");
        for (var g : candidate.entrySet())
            for (Gauge.Member u : Gauge.read(g.getValue(), g.getKey()).methods())
                if (u.name().equals("main")) {
                    boolean entry = u.isPublic() && u.isStatic()
                            && u.returns().equals("void") && u.signature().equals("(String[])");
                    System.out.printf("%-10s %-11s %-6s %-6s %-6s %s%n", g.getKey(), u.signature(),
                            u.isPublic(), u.isStatic(), u.returns(), entry);
                }
    }
}
```

```
class      sig         public static return entry point
Correct    (String[])  true   true   void   true
NotStatic  (String[])  true   false  void   false
NotPublic  (String[])  false  true   void   false
NoArray    (String)    true   true   void   false
Returning  (String[])  true   true   int    false
```

**One** of the five classes carries an entry point. The remaining four each fail on a single
condition, and every failure says something separate. `NotStatic` is not static: calling a
non-static method first requires an instance, but at the program's start there is no instance
yet — the virtual machine cannot call it without creating one. `NotPublic` is not accessible
from outside the class; since the caller is not the class itself, access is closed. `NoArray`
takes a string, not a string array: command-line arguments are an array, and their count is
not known beforehand. `Returning` returns a value; a program's termination information is
given not through a return value, but through a separate call.

That the parameter is a string array is not a formatting detail: arguments given to the
program from outside arrive there, in order, with their count unknown beforehand. The entry
point meets the single piece of data coming from the outside world in this one parameter.

The real observation here is **where** the defect gets caught. All five sources compiled;
their class files were produced. Whether a method is an entry point is not the compiler's
question, it is the virtual machine's. This is the other face of the split the previous lesson
established: the compiler adds steps to the source, while the virtual machine carries its own
conditions for what it will find in the class file.

#### The Class Once, the Instance Every Time

Once the entry point is found, the run comes next. There are two separate levels in preparing
a class: **class-level** work is done once, **instance-level** work is redone with every new
object. Each level also has two steps of its own — field initializers and **static
initializer** blocks. On top of these comes a **constructor**.

For the measurement, the course's concrete axis is used: `Entry`, holding an inventory record.
It has three fields — `name`, `count`, and `weight` in grams. Every initialization step writes
its name to a shared list when it runs; the list is printed at the end.

```java
// Order.java — in what order do the static initializer, field initializer, and constructor run
import java.util.*;

public class Order {
    static final List<String> LOG = new ArrayList<>();
    static void log(String s) { LOG.add(s); }

    static class Entry {
        static { log("class: static initializer"); }
        static final long UNIT_WEIGHT = unitWeight();
        String name;
        int count = startCount();
        long weight;
        { log("instance: initializer block"); }

        Entry(String name, long weight) {
            log("constructor: " + name);
            this.name = name;
            this.weight = weight;
        }

        static long unitWeight() { log("class: field initializer"); return 1L; }
        static int startCount() { log("instance: field initializer"); return 0; }
    }

    public static void main(String[] args) {
        log("-- main started");
        new Entry("bolt", 12L);
        log("-- first instance done");
        new Entry("nut", 7L);
        LOG.forEach(System.out::println);
    }
}
```

```
-- main started
class: static initializer
class: field initializer
instance: field initializer
instance: initializer block
constructor: bolt
-- first instance done
instance: field initializer
instance: initializer block
constructor: nut
```

The dump's first line is the unexpected one: `main` starts **before the class's static
initializer**. A class's preparation is not done at program launch, it is done the moment that
class is really needed for the first time — here, on the first `new` call. If a class is never
used, its static initializer never runs at all.

The second observation is the absence of repetition. On the second `new` call, the two lines
starting with `class:` do **not** show up again; only the instance-level steps repeat.
Class-level work is done once, no matter how many instances are produced. As a number: for two
instances, **2** class-level steps ran, **6** instance-level steps ran.

The `class: field initializer` line in the dump makes another distinction visible too. The
`UNIT_WEIGHT` field is declared `static final`, meaning it will never change again — even so,
its value is produced by **running** something during class initialization, because it comes
from a method call. Had its value been a constant computable at compile time, it would have
taken the previous lesson's constant-folding path, and no step would show up here at all. A
field being unchangeable does not mean its value was obtained without running anything.

Third is the order itself. At both levels, the **field initializer** and the **static initializer** block
follow the order written in the source; in this example the block runs first at class level and the field
initializer first at instance level. The constructor's body is the **last** of these — meaning a
constructor, once its body starts running, can assume the fields have already been initialized. Assigning
a field in the constructor and assigning it at its declaration do not do the same job: the second runs
first, the first overwrites it.

#### Members Not Written in the Source

The previous lesson's table held an `<init>` never written in the source at all. Now it can be
looked at directly — and next to it, a form adding much more can be placed. How many members
does an **enum** with two names written in the source carry in the class file?

```java
// Members.java — members not written in the source, and what happens once a constructor is written
public class Members {
    public static void main(String[] args) throws Exception {
        Gauge.ClassInfo empty = Gauge.read("class Store { }", "Store");
        System.out.println("class Store { }            -> fields " + empty.fields()
                + ", methods " + Gauge.names(empty));
        Gauge.ClassInfo own = Gauge.read("class Store { Store(int count) { } }", "Store");
        System.out.println("class Store { Store(int) } -> fields " + own.fields()
                + ", methods " + Gauge.names(own));

        Gauge.ClassInfo y = Gauge.read("enum Direction { NORTH, SOUTH }", "Direction");
        System.out.println();
        System.out.println("names written in source: 2");
        System.out.println("fields in class file (" + y.fields().size() + "): " + y.fields());
        System.out.println("methods in class file (" + y.methods().size() + "): "
                + Gauge.names(y));
    }
}
```

```
class Store { }            -> fields [], methods [<init>()]
class Store { Store(int) } -> fields [], methods [<init>(int)]

names written in source: 2
fields in class file (3): [NORTH, SOUTH, $VALUES]
methods in class file (5): [values(), valueOf(String), <init>(String, int), $values(), <clinit>()]
```

A class with an entirely empty body carries **one method** in its class file: a no-argument
constructor. Without a single character written in the source, a member has been added; its
name is the **default constructor**, and its only job is to call the base class's constructor.
This is exactly the three-instruction `<init>` row in the previous lesson's table.

The enum goes much further. Against two names written in the source, the class file carries
**three fields** and **five methods**. `NORTH` and `SOUTH` are written; `$VALUES` is not. None
of the methods are written: `values` and `valueOf` are the surface the specification defines
for an enum, `$values` serves them, `<init>` takes two parameters — even though no constructor
call is written in the source at all — and `<clinit>` is the class file's name for the static
initializer block measured in the previous section. This is exactly where the enum's constants
get set up.

This is where the course's third claim is born: **what gets added is not only a step, it is a
member.** A class's surface is not made up only of the members written in its source.

#### A Bounding Measurement: The Member That Disappears on Writing

The output's second line draws the claim's boundary. Once a single-parameter constructor is
written for the `Store` class, the class file again has exactly **one** method, but this time
that method is `<init>(int)` — the very one we wrote. The no-argument constructor has
**disappeared**.

This has a visible consequence, and it is not silent. The same call is put next to two
`Store` forms below and compilation is attempted.

```java
// Effect.java — what happens on the calling side once the added constructor disappears
public class Effect {
    static String attempt(String source) {
        try { Gauge.read(source, "Store"); return "compiled"; }
        catch (Exception e) { return "not compiled"; }
    }

    public static void main(String[] args) {
        String client = " class Client { static Store make() { return new Store(); } }";
        System.out.println("constructor not written + new Store() -> " + attempt("class Store { }" + client));
        System.out.println("constructor written     + new Store() -> "
                + attempt("class Store { Store(int count) { } }" + client));
    }
}
```

```
constructor not written + new Store() -> compiled
constructor written     + new Store() -> not compiled
```

The calling side writes the **same** line in both cases, and even though the line itself did
not change, one compiles and the other does not. No capability was removed from the `Store`
class; the added constructor was displaced **because a constructor was added** to the source.
The compiler places a member only when **none at all** is written; a single written
constructor evicts the added one.

The added step's condition is read from this too. The compiler fills a gap; once the gap is
filled, no addition is made. Knowing what a class's file will hold requires looking not
at what we wrote in the source, but at **what we did not write.**

#### Summary

- A class file does work only once loaded; the entry point is a single signature satisfying
  four conditions at once — public, static, returning nothing, with a single string-array
  parameter, named `main`.
- All five candidate classes compile without a hitch, and only **one** carries an entry point:
  these conditions are not the compiler's question, they are the virtual machine's.
- Class-level initialization runs not at program launch but the first time a class is needed,
  and only **once**; for two instances, **2** class-level and **6** instance-level steps were
  counted.
- At both levels the order is the one written in the source: in this example the static
  initializer runs first at class level, the field initializer first at instance level, and
  the constructor's body last of all.
- A class with an empty body carries a default constructor in its class file; an enum with two
  names written in the source carries **3 fields** and **5 methods** — what is added is not
  only a step, it is a member.
- Once a single-parameter constructor is written, the added no-argument constructor
  disappears: the compiler adds a member only when none at all is written.

#### Next Step

This lesson measured **when** and in what order a class runs, but never asked **how** the
virtual machine finds that class. The names `Store` and `Entry` used in the measurements were
each written standing alone; yet every type name inside a class file, as seen in the previous
lesson, was carried in its **full** form. Where does the gap between a name's short form and
its full form close — at compile time, or during loading? The next lesson measures this: when
the same program is written with an import versus a fully qualified name, are the class files
produced distinguishable from one another, and what does the package name change in that file?

### Packages and Imports

Source: https://academia.sh/en/courses/java-fundamentals/packages-and-imports

An import is not a loading directive, it is a shorthand rule: class files produced when the same program is written with an import versus a fully qualified name are byte-for-byte identical, and a static import adds no step either; a package name, by contrast, changes the file, and two names cannot fit the same shorthand.

The previous lesson measured when a class runs and in what order: the entry point was a single
signature satisfying four conditions at once, class-level initialization ran only once and on
first use, and members not written in the source sat in the class file. But **how** the
virtual machine finds that class was never asked.

The first lesson showed that the class file carries the type it will call by name; the name read
there was abbreviated, while the form stored in the file is **fully qualified**. Yet writing `List`
in the source is enough. The gap between the short name and the full name has to close somewhere —
this lesson measures where. The question is: do the import form and the fully qualified form produce
**separate class files**, or the same one?

#### Namespace, Fully Qualified Name, Import

In Java, a class's real name is not the short name we write. Classes are placed into
**packages**, and a class's **fully qualified name** is the combination of the package name
and the short name. A package is a **namespace** that prevents names from clashing: two
`Proxy` classes in two separate packages are separate classes, because their full names are
separate.

An **import** is a declaration that saves us from writing this full name every time. Its name
is misleading: nothing gets "imported," no file gets loaded, no code gets copied. The
declaration only tells the compiler this — when I write `List` in this file, I mean
`java.util.List`.

Some names need no such declaration at all. Classes in the language's core package — `String`,
`Object`, `Math`, and the like — can be written by their short names in every source file; that
package is open to every file by default. No import appearing in a source file, for this
reason, does not mean "no external class is used." A shorthand existing and a declaration
being written are separate things, and this lesson asks what each leaves in the class file.

The JavaScript and Python curricula each established a module system and package structure in
their own courses; the concepts there are not repeated here. What this lesson asks is separate
and measurable: **does name resolution leave a trace in the class file?** In the same way,
scope and shadowing, established in the Programming Fundamentals course, are not repeated
either; what is measured here is at which stage a name arrives at its full form.

#### The Measurement Core

What is counted in this lesson is neither an instruction nor a member: what is compared is the
class file's **raw bytes**. The core compiles a source, reads the resulting file as is, and
gives two pieces of helper information — the fully qualified name the file carries, and the
line number recorded for a method.

- **LR11** — The compared sources carry the **same line layout**. A class file records a line
  number; if the layout is not held fixed, the comparison measures the line layout, not the
  name's spelling. This is why the measured sources are written on a single line.
- **LR12** — The comparison is made over the class file's whole bytes; no field is left out.
- **LR13** — A source that fails to compile is written as "not compiled"; the compiler's error
  text is not printed, because what is measured is not the text itself, it is compilation's
  result.
- **LR14** — The file name is produced from the short class name, and the directory from the
  package name; the measurement prints no real path.
- **LR15** — The measurement reads no environment-dependent data; the only things counted are
  byte count, line number, and compilation's result.

```java
// Gauge.java — the class file's raw bytes, the name it carries, and the line number it records
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.LineNumber;
import java.nio.file.*;
import java.util.spi.ToolProvider;

class Gauge {
    static byte[] compile(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Path f = d.resolve(className.substring(className.lastIndexOf('.') + 1) + ".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 Files.readAllBytes(d.resolve(className.replace('.', '/') + ".class"));
    }

    static String name(byte[] b) { return ClassFile.of().parse(b).thisClass().asInternalName(); }

    static int lineNumber(byte[] b) {
        for (MethodModel m : ClassFile.of().parse(b).methods())
            if (m.methodName().equalsString("length"))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof LineNumber ln) return ln.line();
        return -1;
    }

    static String attempt(String source, String className) {
        try { compile(source, className); return "compiled"; }
        catch (Exception e) { return "not compiled"; }
    }
}
```

#### An Import Leaves No Trace in the Class File

Four forms of the same class are compiled. The first uses an import, the second writes the
fully qualified name, the third adds one more import that is never used, the fourth is
identical to the first but moves the import to the next line. The first three are single-line,
and this is deliberate — the fourth will soon say why it has to be.

```java
// Main.java — does an import leave a trace in the class file
import java.util.Arrays;

public class Main {
    static String body(String prefix, String type) {
        return prefix + "class Counter { static int length(" + type + " l) { return l.size(); } }\n";
    }

    public static void main(String[] args) throws Exception {
        byte[] imported = Gauge.compile(body("import java.util.List; ", "List"), "Counter");
        byte[] qualified = Gauge.compile(body("", "java.util.List"), "Counter");
        byte[] extraImport = Gauge.compile(
                body("import java.util.List; import java.util.ArrayList; ", "List"), "Counter");
        byte[] importBelow = Gauge.compile("import java.util.List;\n" + body("", "List"), "Counter");
        System.out.printf("%-24s %4d bytes  line %d  name %s%n",
                "import form", imported.length, Gauge.lineNumber(imported), Gauge.name(imported));
        System.out.printf("%-24s %4d bytes  line %d  name %s%n",
                "fully qualified form", qualified.length, Gauge.lineNumber(qualified), Gauge.name(qualified));
        System.out.printf("%-24s %4d bytes  line %d%n",
                "with unused extra", extraImport.length, Gauge.lineNumber(extraImport));
        System.out.printf("%-24s %4d bytes  line %d%n",
                "import on next line", importBelow.length, Gauge.lineNumber(importBelow));
        System.out.println("first two byte-for-byte equal : " + Arrays.equals(imported, qualified));
        System.out.println("first and third equal         : " + Arrays.equals(imported, extraImport));
        System.out.println("first and fourth equal        : " + Arrays.equals(imported, importBelow));
    }
}
```

```
import form               307 bytes  line 1  name Counter
fully qualified form      307 bytes  line 1  name Counter
with unused extra         307 bytes  line 1
import on next line       307 bytes  line 2
first two byte-for-byte equal : true
first and third equal         : true
first and fourth equal        : false
```

The first two files are **byte-for-byte identical**. One of the sources writes `List`, the
other writes `java.util.List`, and the two do not diverge by even a single byte. What this
means is definite: **an import leaves no trace in the class file at all.** The class file
already carries the fully qualified name — the short form is nothing but a rule letting the
compiler find that full name. Name resolution ends at compile time; nothing called an import
ever reaches runtime. The reverse of the same result also holds: writing the fully qualified
form carries no extra cost, because what gets shortened is only the source text.

The third row gives the same result once more. When an entirely unused import is added, the
file stays identical. An unused declaration is not a weight, because a used one is not a
weight either: both cost zero steps.

The fourth row is the measurement's own check. When the import is moved to the next line, the
file stays the **same size** but is no longer byte-for-byte identical, and where the
difference sits is visible: the recorded line number is **2** instead of **1**. So the class
file is not indifferent to everything about the source — it does record line layout. The claim
"leaves no trace" cannot, for this reason, be a loose claim; it holds only once the layout is
held fixed. An equality claim with no counterexample has not really said what it measured.

#### A Package Name Changes the File

If an import leaves no trace, does a package declaration leave none either? Both are lines
sitting at the top of the file. The measurement gives a separate answer.

```java
// Pack.java — does a package name change the class file
public class Pack {
    static final String BODY =
            "class Counter { static int length(java.util.List l) { return l.size(); } }\n";

    public static void main(String[] args) throws Exception {
        byte[] unpackaged = Gauge.compile(BODY, "Counter");
        byte[] packaged = Gauge.compile("package store; " + BODY, "store.Counter");
        System.out.println("unpackaged: " + unpackaged.length + " bytes, name " + Gauge.name(unpackaged));
        System.out.println("packaged  : " + packaged.length + " bytes, name " + Gauge.name(packaged));
    }
}
```

```
unpackaged: 307 bytes, name Counter
packaged  : 313 bytes, name store/Counter
```

Once the package declaration is added, the file grows by **six bytes** and the name it carries
becomes `store/Counter` instead of `Counter`. The added prefix is exactly six characters. Since
the class file carries the name as text, growth is exactly that text's growth.

The distinction comes from here: **an import is the convenience of whoever writes the source;
a package is the class's identity.** The first is valid in one file and ends with that file;
the second is part of the class's name and travels with it everywhere. Two separate classes
came out of the same body, because their fully qualified names are separate.

The measurement itself proves a second thing too. While looking for the compiled file, the
core builds the path by turning the dots in the fully qualified name into slashes; since the
`store.Counter` measurement was read successfully, the compiler must have written that file
under a `store` directory. A package is not a naming convention: it is both part of the name
and it decides where the class will be looked for. A declaration sitting as a single word in
the source finds its counterpart as a directory in the file layout.

#### A Bounding Measurement: Zero Steps, Not Zero Rules

There is one more form of shorthand. **Static import** lets a class's static member also be
written by its short name: `max` instead of `Math.max`. Does this add zero steps too, and does
the shorthand really cost nothing at all?

```java
// StaticImport.java — static import and name clash
import java.util.Arrays;

public class StaticImport {
    static String body(String prefix, String call) {
        return prefix + "class Greatest { static int biggest(int a, int b) { return " + call + "(a, b); } }\n";
    }

    public static void main(String[] args) throws Exception {
        byte[] qualified = Gauge.compile(body("", "Math.max"), "Greatest");
        byte[] statically = Gauge.compile(body("import static java.lang.Math.max; ", "max"), "Greatest");
        System.out.println("Math.max form       : " + qualified.length + " bytes");
        System.out.println("static import form  : " + statically.length + " bytes");
        System.out.println("byte-for-byte equal : " + Arrays.equals(qualified, statically));
    }
}
```

```
Math.max form       : 288 bytes
static import form  : 288 bytes
byte-for-byte equal : true
```

Static import adds no step either; the two files are byte-for-byte identical. The name of the
class owning the method is already written inside the call itself; the short form only erases
that name from the source text.

Every measurement up to here has given zero. Does shorthand really have no cost at all? The
cost is paid not at runtime, it is paid at compile time, and it is real. The five cases below
write the same two classes — two classes sharing a short name, sitting in two separate
packages — in five separate forms.

```java
// Clash.java — under which condition is a name clash a compile error
public class Clash {
    public static void main(String[] args) {
        String[][] cases = {
            {"two exact names, even unused",
             "import java.net.Proxy; import java.lang.reflect.Proxy; class Clashing { }"},
            {"two wildcard imports, unused",
             "import java.net.*; import java.lang.reflect.*; class Clashing { }"},
            {"two wildcard imports, used",
             "import java.net.*; import java.lang.reflect.*; class Clashing { Proxy p; }"},
            {"exact name + wildcard, used",
             "import java.net.Proxy; import java.lang.reflect.*; class Clashing { Proxy p; }"},
            {"no import, two full names",
             "class Clashing { java.net.Proxy a; java.lang.reflect.Proxy b; }"},
        };
        for (String[] c : cases)
            System.out.printf("%-32s %s%n", c[0], Gauge.attempt(c[1], "Clashing"));
    }
}
```

```
two exact names, even unused     not compiled
two wildcard imports, unused     compiled
two wildcard imports, used       not compiled
exact name + wildcard, used      compiled
no import, two full names        compiled
```

Five rows give the rule of name resolution from end to end. First row: importing two classes
by exact name is a compile error **even if those names are never used** — the shorthand table
is built before the source itself, and one short name cannot point to two full ones. The
second and third rows show the reverse: importing an entire package as a wildcard does not by
itself cause a clash; the error only surfaces once the ambiguous name is **used**. A wildcard
import is not a decision, it is a search list.

The fourth row states the order: an import written by exact name takes **priority** over a
wildcard import, so no ambiguity arises. The fifth row draws the boundary — when both classes
are written by their full names, they can be used side by side without a problem. So using the
two together is not forbidden; **fitting them into the same shorthand** is.

The course's measure is read once more from here. The first lesson showed a case where the
added step **was absent** through constant folding; here there are three more cases where the
added step is absent, and in all three, the side paying the cost is the compiler. A form
costing nothing at runtime does not mean that form has no rules.

#### Summary

- A class's real name is its fully qualified name; a package is a namespace, and the class file
  carries this name as text.
- The class files produced by the import form and the fully qualified form are **byte-for-byte
  identical**; an unused import does not change the file either. Name resolution ends at
  compile time.
- A class file records the source's line layout: when an import is moved to the next line, the
  file stays the same size but is no longer identical, because the recorded line number becomes
  2 instead of 1.
- A package declaration **changes** the file: the name it carries becomes `store/Counter`
  instead of `Counter`, and the file grows by exactly as many bytes as the added prefix.
- Static import adds zero steps too; on the other hand, importing two classes sharing a short
  name by exact name is a compile error even if those names are never used. In a wildcard
  import, the error surfaces only once the ambiguous name is used, and an exact-name import
  takes priority.
- Shorthand's runtime cost is zero, its compile-time rule is real: one short name in one file
  can point to only one full name, but two classes can be used side by side by their full
  names.

#### Next Step

Throughout this topic, what got measured was always **what the compiler adds**: a step, a
member, a name. In all three, the decision was made at compile time, and only the result
reached runtime — which class was meant was already settled by the time the class file was
written. If name resolution ends at compile time, when is **the value itself** decided? The
next topic starts measuring this from the crossing between a primitive type and a wrapper: an
integer assigned to a wrapper is a single equals sign in the source, and a call producing an
object in the class file.

## Basic Syntax

Autoboxing between primitive types and wrappers, the scope and lifetime of variable kinds, an operator's numeric promotion, control-flow forms, an array's fixed-size layout, a string's immutability, and argument passing's single model.

### Primitive Types and Wrappers

Source: https://academia.sh/en/courses/java-fundamentals/primitive-types-and-wrappers

A one-letter source difference is a method call in the class file: the line Integer count = 5 adds Integer.valueOf, the reverse direction adds Integer.intValue; the same 200 boxings produce between 100 and 200 objects depending on the warehouse, and where the cache ends, identity and value come apart.

The previous lesson measured that name resolution finishes at compile time: an import line
leaves no trace in the class file, because the compiler resolves the name and puts its fully
qualified counterpart in its place. One question is left. If name resolution finishes at
compile time, when and where does the **value itself** the name carries get decided?

Java does not give a single answer to this question. When `int count = 5` is written, the
value sits in the method's own local slot, as the number itself. When `Integer count = 5` is
written, an object is found on the heap and the local slot holds only that object's
reference. The difference in the source is one letter; the difference in the class file is a
**method call.** The Programming Fundamentals course established basic data types and
implicit conversion as concepts — **that is not repeated here.** There, what a value's type
determines was explained; here, what is measured is how many **steps** the compiler puts into
the class file for this crossing.

#### Two Type Families

Java's types split in two. **Primitive types** number eight and carry nothing but a number;
**reference types** carry an object's address. Every primitive type has a **wrapper** class:
a reference type that holds the same value inside an object.

| Primitive type | Width (bits) | Wrapper |
|---|---|---|
| `boolean` | unspecified | `Boolean` |
| `byte` | 8 | `Byte` |
| `short` | 16 | `Short` |
| `char` | 16 | `Character` |
| `int` | 32 | `Integer` |
| `long` | 64 | `Long` |
| `float` | 32 | `Float` |
| `double` | 64 | `Double` |

The distinction's visibility in the source is low, because the crossing between the two
families is never written. An `int` value enters a spot expecting an `Integer` on its own
(**autoboxing**), and an `Integer` enters a spot expecting an `int` on its own
(**unboxing**). In both directions, only an equals sign appears in the source.

This is also where the distinction's reason to exist lies. A primitive type is not an object:
it has no methods, it cannot be `null`, and it **cannot enter any spot expecting a
reference.** A list, a map, or any structure taking a type variable stores only references;
to put an `int` value in a list, an object to wrap it is needed first. The wrapper fills this
gap, and autoboxing is a shorthand that **erases** that filling from the source. The result is
this: a source that writes `List<Integer>` does not do the same work as one that writes
`int` — there is an object layer in between, and that layer's cost sits in the class file.

A single data carrier is used across the course: `Item`, a warehouse record with three
fields — `String name`, `int count`, `long weight` (grams). This lesson measures boxing on
the **`count` field.**

#### The Measurement Core

The measurement compiles a given source text by calling the compiler from within the program,
reads the class file it produces, and returns two things for every method: how many
instructions were produced and which calls that method contains. The compiler and the class
file reader are part of the standard library.

- **BS1** — The oracle is the setup itself: since we wrote the measured source ourselves, we
  know what we wrote. The right column lists **all** calls in the class file; the reader
  drops what was written in the source, and what is left is **what the compiler added.**
- **BS2** — The measured source text is fixed and is given to the same compiler on every run;
  the numbers come from a single compiler run and are deterministic within that run.
- **BS3** — The constructor (`<init>`) is not taken into the table; members not written in
  the source are a separate matter.

```java
// Gauge.java — reads what the compiler adds to 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 Method(String name, int instructions, List<String> added) {}

    static List<Method> read(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Files.writeString(d.resolve(className + ".java"), source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(sink, sink, "-d", d.toString(),
                d.resolve(className + ".java").toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(className + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof InvokeInstruction iv)
                    added.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof TypeCheckInstruction t && t.opcode() == Opcode.CHECKCAST)
                    added.add("type check " + shortName(t.type().asInternalName()));
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, added));
        }
        return methods;
    }

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

#### The Call Assignment Adds

The measured source carries five methods. `box` binds an `int` value to an `Integer` name,
`unbox` does the reverse. `arraySum` and `listSum` write **the same loop**; the only
difference is whether what is walked is an `int[]` or a `List<Integer>`. `tally` updates a
counter with a `count` value.

```java
// Boxing.java — the trace the primitive-to-wrapper crossing leaves in the class file
public class Boxing {
    static final String SOURCE = """
        import java.util.List;
        import java.util.Map;
        class Warehouse {
            static Object box(int count) { Integer k = count; return k; }
            static int unbox(Integer k) { int count = k; return count; }
            static int arraySum(int[] counts) { int t = 0; for (int x : counts) t += x; return t; }
            static int listSum(List<Integer> counts) { int t = 0; for (int x : counts) t += x; return t; }
            static void tally(Map<String, Integer> counter, String name, int count) { counter.put(name, count); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-14s %6s  %s%n", "method", "instr", "call in class file");
        int total = 0;
        for (Gauge.Method y : Gauge.read(SOURCE, "Warehouse")) {
            if (y.name().equals("<init>")) continue;
            System.out.printf("%-14s %6d  %s%n", y.name(), y.instructions(),
                    y.added().isEmpty() ? "-" : String.join(", ", y.added()));
            total += y.added().size();
        }
        System.out.println("total: " + total);
    }
}
```

```
method          instr  call in class file
box                 5  Integer.valueOf
unbox               5  Integer.intValue
arraySum           24  -
listSum            20  List.iterator, Iterator.hasNext, Iterator.next, type check Integer, Integer.intValue
tally               7  Integer.valueOf, Map.put
total: 9
```

`box`'s source writes no call at all; the class file has **`Integer.valueOf`.** `unbox`
writes no call either; the class file has **`Integer.intValue`.** Autoboxing is not a
language convenience, it is a **method call** invisible in the source, and the reverse holds
too. The `tally` row shows how BS1 gets read: `Map.put` is written in the source and is
dropped; `Integer.valueOf` is not written and stays.

The bottom two rows pay off the course's second thesis. `arraySum` produces **24**
instructions and carries **no** step unwritten in the source: the enhanced `for` turns into a
counted loop over an array, and the summation is done directly on numbers. `listSum` produces
**20** instructions — fewer — but carries **five** steps not written in the source: an
iterator is taken, queried twice, the returned object is checked against `Integer`, and
opened into a number with `intValue`. **Fewer instructions, more added steps.** Instruction
count and added-step count do not move in the same direction; a measurement cannot be
compared without saying which one is being counted.

Four of these five steps sit inside the loop body, meaning they are paid per element. The
class file does not show this; what it shows is the step's **existence.** Seeing how many
times it is paid requires running it.

#### Same Number of Boxings, Two Distinct Object Counts

`Integer.valueOf` does not produce a new object on every call. The specification requires
that for `int` values, boxing of **every value between -128 and 127** return **the same
object**; outside this range is left to the implementation. This has a measurable
consequence: the same number of boxings produces a distinct number of objects depending on
where the values fall.

- **BS4** — All three warehouses carry **100** records, and each record's `count` value is
  separate; the only difference is the base the values start from. In each warehouse every
  `count` is boxed **twice**, meaning all three measurements make **200** boxing calls.
- **BS5** — Distinct object count is found by gathering the produced wrappers in a map keyed
  by their identity; no object's identity value is printed, only **how many distinct** objects
  there are is counted.

```java
// Production.java — same number of boxings, two separate object counts
import java.util.IdentityHashMap;
import java.util.Map;

public class Production {
    record Item(String name, int count, long weight) {}

    static Item[] warehouse(int n, int base) {
        Item[] d = new Item[n];
        for (int i = 0; i < n; i++) d[i] = new Item("K" + i, base + i, 1000L + i);
        return d;
    }

    static int distinctObjects(Item[] warehouse) {
        Map<Integer, Boolean> identity = new IdentityHashMap<>();
        for (int round = 0; round < 2; round++)
            for (Item k : warehouse) identity.put(k.count(), Boolean.TRUE);   // every put is one valueOf
        return identity.size();
    }

    public static void main(String[] args) {
        System.out.printf("%-22s %8s %8s %10s%n", "warehouse", "boxings", "values", "distinct obj");
        for (int base : new int[] {1, 100, 1000}) {
            Item[] d = warehouse(100, base);
            System.out.printf("count %4d..%-4d %8d %8d %10d%n",
                    base, base + 99, 2 * d.length, d.length, distinctObjects(d));
        }
        Item[] big = warehouse(100, 1000);
        long t = 0;
        for (Item k : big) t += k.count();
        System.out.println("primitive sum: " + t + " | wrappers produced: 0");
    }
}
```

```
warehouse               boxings   values distinct obj
count    1..100       200      100        100
count  100..199       200      100        172
count 1000..1099      200      100        200
primitive sum: 104950 | wrappers produced: 0
```

The same code runs in all three rows, the same number of boxings is done, and the same number
of distinct values is boxed. The number of objects produced is **100**, **172**, and **200**.
Nothing in the source produces this difference; the difference is entirely a decision made by
the added step, that is, by `Integer.valueOf`.

The middle row shows the boundary directly. When `count` values spread between 100 and 199,
the 28 values below 128 come back from the cache and **one** object forms for each; the
remaining 72 values produce **two distinct** objects across the two rounds. Total 28 plus
144, that is **172.** The number itself marks a threshold.

The last row is the measure's other end. When the same warehouse is summed over `int`, no
wrapper is produced at all: the line `t += k.count()` has no boxing, because `long` and `int`
are both primitive types. The cost of using a wrapper in a loop can be that one object is
produced at every step of that loop — and this is written nowhere in the source.

#### Where Identity and Value Come Apart

This difference in object production does not stay a number only; it has a **visible
consequence.** When two reference types are compared with `==`, what is tested is not their
values but **whether they are the same object.** In the cache range, two boxings give the
same object so `==` seems to give the correct result; outside the range the same code gives
the wrong result.

```java
// Cache.java — where a wrapper's identity and value come apart
public class Cache {
    record Item(String name, int count, long weight) {}

    static Item searchByIdentity(Item[] warehouse, Integer wanted) {
        for (Item k : warehouse) {
            Integer count = k.count();          // boxing: Integer.valueOf
            if (count == wanted) return k;       // two wrappers: identity comparison
        }
        return null;
    }

    static Item searchByValue(Item[] warehouse, Integer wanted) {
        for (Item k : warehouse) {
            Integer count = k.count();
            if (count.equals(wanted)) return k;  // value comparison
        }
        return null;
    }

    public static void main(String[] args) {
        Item[] warehouse = { new Item("bolt", 127, 40L), new Item("screw", 128, 90L) };
        System.out.printf("%-10s %-12s %-12s%n", "wanted", "by identity", "by value");
        for (Integer wanted : new Integer[] {127, 128}) {
            Item a = searchByIdentity(warehouse, wanted), b = searchByValue(warehouse, wanted);
            System.out.printf("count %-5d %-12s %-12s%n", wanted,
                    a == null ? "not found" : a.name(), b == null ? "not found" : b.name());
        }
        System.out.println();
        Integer a = 127, b = 127, c = 128, d = 128;
        System.out.println("Integer 127 == 127 : " + (a == b) + " | equals: " + a.equals(b));
        System.out.println("Integer 128 == 128 : " + (c == d) + " | equals: " + c.equals(d));
        Long e = 127L, f = 127L;
        Double g = 1.0, h = 1.0;
        System.out.println("Long    127 == 127 : " + (e == f) + " | equals: " + e.equals(f));
        System.out.println("Double  1.0 == 1.0 : " + (g == h) + " | equals: " + g.equals(h));
    }
}
```

```
wanted     by identity  by value    
count 127   bolt         bolt        
count 128   not found    screw       

Integer 127 == 127 : true | equals: true
Integer 128 == 128 : false | equals: true
Long    127 == 127 : true | equals: true
Double  1.0 == 1.0 : false | equals: true
```

`searchByIdentity` finds the record with count 127, **fails to find** the one with count 128.
There is no difference at all between the two searches in the source; the result changed
because the sought value went up by one. This is the clearest example of the course's fourth
reading: **the decision was given not by the programmer, but by the added step.** The same
code works correctly on small warehouses, incorrectly on large ones, and the compiler
produces no warning.

The bottom table draws the boundary. For `Integer` and `Long`, 127 is equal by identity, 128
is not; for `Double`, even 1.0 is not equal, because fractional wrappers carry no cache. The
`equals` column is **correct in all four rows**: value comparison is not affected by which
object boxing returns. The rule follows from here — wrappers are compared with `equals`, not
`==`; `==` is a value comparison only for primitive types.

The same observation also gives the bounding measurement: there is a range where this
lesson's measured added step has **no** effect on identity at all. Between -128 and 127,
boxing produces no new object, `==` behaves as expected, and object count equals value count.
The added step's visibility depends on which range the value falls into.

#### Summary

- The crossing between a primitive type and its wrapper is an equals sign in the source, a
  call in the class file: boxing adds **`Integer.valueOf`**, unboxing adds
  **`Integer.intValue`.**
- The same loop produces **24** instructions and **0** added steps over an array, **20**
  instructions and **5** added steps over `List<Integer>`; fewer instructions, more added
  steps.
- `Integer.valueOf` does not produce a new object on every call: the same 200 boxings leave
  **100**, **172**, or **200** distinct objects depending on the values' range.
- The specification requires the same object to be returned between -128 and 127; `==` looks
  correct in this range, gives the wrong result outside it, and the compiler produces no
  warning.
- Wrappers are compared with `equals`; `equals` gave the correct result in all four
  measurements, `==` is a value comparison only for primitive types.

#### Next Step

This lesson measured which form a value is held in: the number itself, or an object wrapping
the number. What is left is the name itself. The next lesson writes the same number of names
into three separate places — inside a method, on an instance, and on the class itself — and
counts where these three names land in the class file: which one is written into a **slot**,
which one into a **field**, why a name opened inside a block never shows up as a name at all
in the class file, and why redeclaring the same name in an inner block gives a compile error.

### Variables and Scope

Source: https://academia.sh/en/courses/java-fundamentals/variables-and-scope

The same int count declaration compiles to three separate things depending on where it sits: a field in the class body, a slot in a method body, statically the class's single-copy field; a method using the same name in two sibling blocks produces the same 20 instructions with one fewer slot.

The previous lesson measured which form a value is held in: the number itself, or an object
wrapping the number. The difference was one letter in the source, a call in the class file.
This lesson asks the same question of the name itself. The line `int count = 5` compiles to
three separate things depending on where it is written; the writing of all three in the
source is **identical**.

The Programming Fundamentals course established variables, binding, scope, and lifetime as
concepts — **those are not repeated here.** There, where a name is visible was explained.
Here, what is measured is **which place** the compiler gives that name in the class file: a
field sitting in the class body, or a slot in the local space set aside per call for the
method.

#### Three Name Families

Java writes a name into one of three places, and the choice is decided by **where the
declaration sits.**

- A **local variable** is declared in a method's body. It does not appear as a name in the
  class file; it holds a numbered **slot** in the method's local space. A new copy forms
  every time the method is called and disappears when the call ends. Parameters are local
  variables too.
- An **instance variable** is declared in the class body, without `static`. It stands as a
  **field** in the class file and has a separate copy in every object.
- A **class variable** is declared in the class body with `static`. It is a field too, but
  its copy is **single**: it belongs to the class independent of object count.

The distinction depends on a keyword and an indentation in the source; in the class file it
depends on two separate structures. Fields stand by name in the class file's field table.
Local variables are not there at all — the method's code uses not names but **numbers.** This
lesson measures the three families' distinction on the `count` field of the record carried
through the course.

#### The Measurement Core

The core is the previous lesson's core, with two measurements added: the **slot count** each
method uses, and the **field accesses** in that method. The class's field table is read too.
If compilation fails, no class file is produced, and the core reports this separately.

- **BS6** — Slot count is the size of the local space the class file sets aside for that
  method — not the number of names written in the source. In instance methods, **slot number
  zero is reserved for `this`**; in static methods, it starts from the first parameter.
- **BS7** — The field-access column carries the instruction name: `getfield` and `putfield`
  access an instance's field, `getstatic` and `putstatic` access the class's field.

```java
// Gauge.java — reads where a name lands in the class file: a field, or a slot
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.attribute.CodeAttribute;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Gauge {
    record Method(String name, int instructions, int slots, List<String> fieldAccess) {}
    record ClassInfo(List<String> fields, List<Method> methods) {}

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

    static ClassInfo read(String source, String className) throws Exception {
        Path c = compile(source, className);
        if (c == null) throw new IllegalStateException("did not compile: " + className);
        ClassModel cm = ClassFile.of().parse(c);
        List<String> fields = new ArrayList<>();
        for (FieldModel f : cm.fields()) fields.add(f.fieldName().stringValue());
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            CodeAttribute code = (CodeAttribute) m.code().get();
            List<String> access = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : code) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof FieldInstruction fi)
                    access.add(fi.opcode().name().toLowerCase(Locale.ROOT)
                            + " " + fi.name().stringValue());
            }
            methods.add(new Method(m.methodName().stringValue(), instructions,
                    code.maxLocals(), access));
        }
        return new ClassInfo(fields, methods);
    }
}
```

#### Eight Names, Three Separate Places

The measured class declares `totalCount` and `count` in the class body, and `incoming`,
`updated`, `n`, `t`, `temp`, and `second` in method bodies. `blockScope` and `noBlock` do
**the same work in the same order**; the only difference between them is whether the two
temporary names are opened inside curly braces.

```java
// Location.java — four names, three separate places
public class Location {
    static final String SOURCE = """
        class Warehouse {
            static int totalCount = 0;
            int count;
            void add(int incoming) {
                int updated = count + incoming;
                count = updated;
                totalCount += incoming;
            }
            static int blockScope(int n) {
                int t = 0;
                { int temp = n * 2; t += temp; }
                { int second = n * 3; t += second; }
                return t;
            }
            static int noBlock(int n) {
                int t = 0;
                int temp = n * 2; t += temp;
                int second = n * 3; t += second;
                return t;
            }
            static long wide(long weight, int extra) {
                long updated = weight + extra;
                return updated;
            }
            int visible(int count) { return count; }
            int accessesField(int count) { return this.count; }
        }
        """;

    public static void main(String[] args) throws Exception {
        Gauge.ClassInfo s = Gauge.read(SOURCE, "Warehouse");
        System.out.println("fields in class file: " + s.fields());
        System.out.printf("%-14s %6s %5s  %s%n", "method", "instr", "slots", "field access");
        for (Gauge.Method y : s.methods())
            System.out.printf("%-14s %6d %5d  %s%n", y.name(), y.instructions(), y.slots(),
                    y.fieldAccess().isEmpty() ? "-" : String.join(", ", y.fieldAccess()));
    }
}
```

```
fields in class file: [totalCount, count]
method          instr slots  field access
<init>              3     1  -
add                13     3  getfield count, putfield count, getstatic totalCount, putstatic totalCount
blockScope         20     3  -
noBlock            20     4  -
wide                7     5  -
visible             2     2  -
accessesField       3     2  getfield count
<clinit>            3     0  putstatic totalCount
```

The field table has **two** names: `totalCount` and `count`. Of the eight names declared in
the source, only two stand as names in the class file. The remaining six are not names
anywhere; they live only as slot numbers. A local variable's name **does not enter** the
class file — by the time the compiler's work is done, the name has been spent. If the
compiler is separately told to, names can be added to the class file as debug information,
but this is not part of the code: the method's instructions use the same slots without that
information either. Field names, by contrast, are not optional, because they are accessed
from outside the class and the accessing code has to know the name.

The `add` row shows all three families at once. In the same method, `count` appears twice
with an instance-field instruction (`getfield`, `putfield`), `totalCount` twice with a
class-field instruction (`getstatic`, `putstatic`); `incoming` and `updated` enter no field
instruction at all, because they are in slots. Three slots are counted: zero `this`, one
`incoming`, two `updated`.

The measurement's real row is the pair in the middle. `blockScope` and `noBlock` produce
**the same 20 instructions** — the work done at run time is identical, bit for bit. Slot
count is separate: **3** and **4**. Curly braces do nothing at run time; the work they do
finishes at compile time. Because the compiler knows the name `temp` dies at the end of the
first block, it gives the name `second` **the same slot.** Block scope is a concept that does
not exist at run time: all that is left of it in the class file is **one fewer slot.**

The `wide` row shows from another angle that a slot is not a name count: three names are
declared, **five** slots are counted. A slot is a fixed width, and 64-bit values do not fit
in it; `long weight` holds two slots, `int extra` one slot, `long updated` two slots again.
The local space's size depends not on how many names are written but on **which type** those
names are — this is the class-file counterpart of the width column in the previous lesson's
table.

The last two rows separate shadowing. `visible` and `accessesField` carry the same signature
and the name `count` appears in both; one makes **no** field access at all, the other does a
`getfield`. The `count` declared as a parameter **shadows** the `count` that is a field; the
only way to reach the field is to write `this.count`. Which one gets read is decided at
compile time and stands as two separate instructions in the class file.

Both corners of the table carry meaning too. `<init>` and `<clinit>` are methods not written
in the source; the first is the constructor preparing the `count` field, the second is the
class-initialization block carrying the `totalCount = 0` assignment. Both were measured in
the previous topic. `<clinit>`'s slot count being **zero** is meaningful here: there is no
`this` in a class-initialization block, because there is not yet any object in the picture.

#### Scope's Visible Result

The distinction of place has a consequence visible not as a number but **as a value.** When
two objects are produced from the same class, the instance variable splits in two, the class
variable does not.

- **BS8** — Two objects are produced and the third call is made to the second object; because
  the class variable carries the total of all calls, the expected value is 3 plus 4 plus 10.
- **BS9** — The shadowing test is done at a point where previous calls have already changed
  the field, so it can be told apart from the value whether the parameter or the field is
  being read.

```java
// Scope.java — the visible result of a field's and a slot's lifetime
public class Scope {
    static class Warehouse {
        static int totalCount = 0;                 // class variable: single copy
        int count;                                  // instance variable: one copy per instance

        void add(int incoming) {
            int updated = count + incoming;         // local variable: one copy per call
            count = updated;
            totalCount += incoming;
        }

        int visible(int count) { return count; }     // parameter shadows the field
        int accessesField(int count) { return this.count; }
    }

    public static void main(String[] args) {
        Warehouse bolt = new Warehouse(), screw = new Warehouse();
        bolt.add(3);
        bolt.add(4);
        screw.add(10);
        System.out.println("bolt.count: " + bolt.count + " | screw.count: " + screw.count
                + " | Warehouse.totalCount: " + Warehouse.totalCount);
        System.out.println("visible(9): " + bolt.visible(9)
                + " | accessesField(9): " + bolt.accessesField(9));
    }
}
```

```
bolt.count: 7 | screw.count: 10 | Warehouse.totalCount: 17
visible(9): 9 | accessesField(9): 7
```

`count` is separate in every object: **7** and **10**. `totalCount` is single: **17**, that
is, the sum of three calls. `updated` shows up nowhere, because it is destroyed at the end of
every call. The three families' lifetimes are separated this way too — a slot lives for the
call, an instance field for the object, a class field for as long as the class stays loaded.

The second line shows shadowing's cost. Two calls to the same object with the same argument
return **9** and **7**. There is no difference between the two methods' bodies in the source
except `this.`. A parameter carrying the same name as a field makes that field unreachable
throughout the method; what gets read is no longer the object's state, it is the argument
coming from the caller.

#### The Bounding Measurement: Which Redeclaration Passes

Shadowing does not happen everywhere. Redeclaring the same name in nested blocks in Java is a
**compile error**; shadowing only happens between two names from separate families, that is,
between a field and a local. The five forms below are placed into the same class skeleton,
and only whether they compile is checked.

- **BS10** — All five cases are written into the same body, with the same class name; the
  measurement looks not at the text the compiler produces but at **whether the class file
  forms.** The compiler's message is not printed.

```java
// Declaration.java — which redeclaration passes the compile stage
public class Declaration {
    static final String[][] CASES = {
        {"same name in two sibling blocks",
         "int t = 0; { int g = n; t += g; } { int g = n; t += g; } return t;"},
        {"redeclaring the outer name in an inner block",
         "int t = 0; { int t = n; } return t;"},
        {"accessing an inner-block name from outside",
         "int t = 0; { int g = n; } return t + g;"},
        {"local name same as a field name",
         "int count = n; return count + this.count;"},
        {"local name same as a parameter name",
         "int n = 1; return n;"},
    };

    public static void main(String[] args) throws Exception {
        System.out.printf("%-34s %s%n", "declaration form", "compile");
        for (String[] d : CASES) {
            String source = "class D { int count = 5; int measure(int n) { " + d[1] + " } }";
            System.out.printf("%-34s %s%n", d[0],
                    Gauge.compile(source, "D") != null ? "passes" : "error");
        }
    }
}
```

```
declaration form                   compile
same name in two sibling blocks    passes
redeclaring the outer name in an inner block error
accessing an inner-block name from outside error
local name same as a field name    passes
local name same as a parameter name error
```

The two passing rows share something: in neither is there a point where **two names are
visible at the same time.** The `g` names in sibling blocks never see each other, because the
first has closed before the second opens — this is the source-side counterpart of slot
sharing. A field and a local, by contrast, are from separate families, and the compiler can
tell which one is meant by `this.`.

The three errors share something too: in every one, a single local name has been declared
twice or used outside its scope. In Java, a local variable's name **cannot be redeclared**
within its scope; an inner block cannot shadow an outer one. The fifth row is the narrowest
form of this — a parameter is a local variable too, so redeclaring the same name in the body
is an error. The boundary is drawn cleanly this way: shadowing exists only between a field
and a local, not between two locals, and the compiler rejects the second case before a class
file is ever produced.

It should also be written that this prohibition has a cost: because a short, meaningful name
in an inner block cannot be used a second time, names pile up in long methods. What is gained
in return is that a name has **a single declaration** at any point in a method body — the
reader who sees a name never has to search for which declaration it refers to. The
measurement shows this decision has no run-time counterpart at all: both passing forms
produce the same instructions in the class file.

#### Summary

- Where a declaration sits decides where the name lands in the class file: names in the class
  body enter the **field** table, names in a method body only hold a numbered **slot** and
  never appear as a name in the class file.
- The `add` method shows all three families at once: `getfield`/`putfield` for `count`,
  `getstatic`/`putstatic` for `totalCount`, no field instruction at all for `incoming` and
  `updated`.
- Block scope ends at compile time: `blockScope` and `noBlock` produce **the same 20
  instructions**, the block form uses only **one fewer slot.**
- An instance variable lives per object, a class variable per class, a local variable per
  call; the measurement's values **7**, **10**, and **17** separate these three lifetimes.
- Shadowing happens only between a field and a local and is resolved with `this.`;
  redeclaring a local name in an inner block is a compile error.

#### Next Step

This lesson measured where a name is written. Once the values names carry settle into their
places, next come the operators that combine them. The next lesson counts what one-character
forms like `+` and `+=` leave behind in the class file: the conversion the compiler inserts
when an `int` and a `long` are added, the compound-assignment form's silent addition of a
narrowing never written in the source, and its **visible** consequence; short-circuit
evaluation showing up in the class file as a branch instruction — and where two constants are
added, no step being added at all.

### Operators and Expressions

Source: https://academia.sh/en/courses/java-fundamentals/operators-and-expressions

One-character forms leave silent steps in the class file: adding an int and a long inserts a conversion, count += 300 puts in a narrowing never written in the source and the result comes out 45, short-circuit shows up as a branch instruction — with constants, no step is added at all.

The previous lesson measured where a name is written in the class file: into a field, or into
a slot. Once names have settled into their places, next come the operators that combine them.
An operator is one character in the source; in the class file it is one or several
instructions, and part of those instructions is never written in the source at all. This
lesson counts that part.

The Programming Fundamentals course established operators, precedence, short-circuit
evaluation, and explicit/implicit conversion as concepts — **those are not repeated here.**
There, when a conversion is widening versus narrowing, and why narrowing can produce silent
information loss, was explained. What is measured here is **who adds** that narrowing: a line
that writes no conversion anywhere in the source can carry a conversion instruction in the
class file.

#### An Operator Is a Shorthand

Java's arithmetic operators only work at four widths: `int`, `long`, `float`, and `double`.
There is no separate addition instruction for `byte`, `short`, and `char`. The consequence is
this: values in smaller types are promoted to `int` before entering the operation, and if the
two operands have separate widths, the narrower one is converted to the wider one. This is
called **numeric promotion**, and it is invisible in the source.

Promotion has a reverse too. Compound assignment — `+=`, `-=`, `*=` — has to preserve the
left side's type. When an `int` is added to a `byte` variable with `+=`, the addition is done
at `int` width, then the result is **narrowed** back to `byte`. This narrowing is not written
in the source; if it were written, the compiler would not want it anyway. The same operation
written as `count = count + 300` would give a compile error, because there the programmer
would have to write the narrowing.

The real distinction between the two directions is safety. A crossing from a narrow type to a wide one is
**widening** and loses no information; the compiler does it without asking. A crossing from a wide type to
a narrow one is **narrowing**, and bits that do not fit are discarded; the compiler does not normally do
this, it asks the programmer to write it. Compound assignment is **one of the gaps** in this rule: the
left side's type is already known, so the compiler puts the narrowing in itself, right where it would
otherwise ask for it. The measurement looks for this gap's counterpart in the class file.

The lesson measures these two directions on two fields of the record carried through the
course: `int count` and `long weight`. The width difference between them is the narrowest
example on which promotion can be measured.

#### The Measurement Core

The core is the previous lessons' core; this lesson reads two measurements. The first is
**conversion instructions** — instructions in the class file that convert one width to
another form a separate family, and their names carry the two types converted (`i2l`,
`i2b`). The second is **branch instruction count**: every instruction that carries the flow
from one point to another is counted.

- **BS11** — The right column lists conversions, dynamic calls, and calls in the class file;
  the reader drops what was written in the source, and what is left is what the compiler
  added.
- **BS12** — All measured methods are static and their bodies are a single line; the
  difference in instruction count comes not from method-call overhead but only from the
  expression itself.

```java
// Gauge.java — reads the conversion, call, and branch instruction an operator leaves 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 Method(String name, int instructions, int branches, List<String> added) {}

    static List<Method> read(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Files.writeString(d.resolve(className + ".java"), source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(sink, sink, "-d", d.toString(),
                d.resolve(className + ".java").toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(className + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0, branches = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof BranchInstruction) branches++;
                if (e instanceof ConvertInstruction c)
                    added.add("convert " + c.opcode().name().toLowerCase(Locale.ROOT));
                else if (e instanceof InvokeDynamicInstruction id)
                    added.add("dynamic:" + id.name().stringValue());
                else if (e instanceof InvokeInstruction iv)
                    added.add(iv.name().stringValue());
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, branches, added));
        }
        return methods;
    }
}
```

#### Thirteen Expressions, What They Leave in the Class File

The measured class carries thirteen methods, each a single expression. `narrow` and
`explicitNarrow` do the same work; the only difference is whether the narrowing is written in
the source. `lateConvert` and `earlyConvert` do the same multiplication; the only difference
is whether the conversion is asked for before or after the multiplication. `constantArithmetic`,
`finalConstantProduct`, and `variableProduct` give **the same product** in three separate
forms.

```java
// Operators.java — thirteen expressions, what they leave in the class file
public class Operators {
    static final String SOURCE = """
        class Warehouse {
            static long promote(int count, long weight) { return count + weight; }
            static byte narrow(byte count) { count += 300; return count; }
            static byte explicitNarrow(byte count) { count = (byte) (count + 300); return count; }
            static int smallSum(byte count, short shelf) { return count + shelf; }
            static long lateConvert(int count, int unit) { return count * unit; }
            static long earlyConvert(int count, int unit) { return (long) count * unit; }
            static String concat(String name, int count) { return name + count; }
            static String constantConcat() { return "bo" + "lt"; }
            static int constantArithmetic() { return 24 * 60 * 60; }
            static final int SHELVES = 24;
            static int shelfCount = 24;
            static int finalConstantProduct() { return SHELVES * 60 * 60; }
            static int variableProduct() { return shelfCount * 60 * 60; }
            static boolean shortCircuit(int count, long weight) { return count > 0 && weight > 0; }
            static boolean fullEval(int count, long weight) { return (count > 0) & (weight > 0); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s %4s  %s%n", "method", "instr", "br", "step not written in source");
        for (Gauge.Method y : Gauge.read(SOURCE, "Warehouse")) {
            if (y.name().startsWith("<")) continue;
            System.out.printf("%-18s %6d %4d  %s%n", y.name(), y.instructions(), y.branches(),
                    y.added().isEmpty() ? "-" : String.join(", ", y.added()));
        }
    }
}
```

```
method              instr   br  step not written in source
promote                 5    0  convert i2l
narrow                  7    0  convert i2b
explicitNarrow          7    0  convert i2b
smallSum                4    0  -
lateConvert             5    0  convert i2l
earlyConvert            6    0  convert i2l, convert i2l
concat                  4    0  dynamic:makeConcatWithConstants
constantConcat          2    0  -
constantArithmetic      2    0  -
finalConstantProduct      2    0  -
variableProduct         6    0  -
shortCircuit           10    3  -
fullEval               14    4  -
```

The first row is numeric promotion's name in the class file. In the expression
`count + weight`, `count` is an `int`, `weight` is a `long`; the addition instruction cannot
take both at once, so the compiler inserts an **`i2l`** in between. It writes no conversion in
the source. One of five instructions is a conversion the programmer did not write.

The second row is this lesson's quietest step. The form `count += 300` produces seven
instructions and one of them is an **`i2b`**: the addition is done at `int` width, the result
is narrowed to `byte`. The compiler adds the narrowing on its own. This is the class file's
counterpart of the silent information loss explained in the previous course — the concept was
established there, here its point of insertion is shown.

The row below measures how exactly identical this is. `explicitNarrow` writes the narrowing
**in the source** and comes out the same as `narrow` in the class file: the same **7**
instructions, the same `i2b`. The two forms are indistinguishable in the class file. `+=` is
not an operator, it is a **shorthand** for this line; what it shortens has a conversion inside
it, and the shorthand hides it.

The fourth row shows that promotion does not always produce an instruction. When a `byte` and
a `short` are added, both are promoted to `int`, but there is **no conversion at all** in the
class file: four instructions, zero added steps. These types are already loaded at `int`
width, so promotion pays no instruction. Promotion exists everywhere as a rule; as an
instruction it shows up only when a width actually changes.

The next two rows compare conversion's **location**. Both return the same product as a
`long`. `lateConvert` carries a single `i2l`, and that conversion is **after** the
multiplication: two `int`s are multiplied, the result is converted to `long`.
`earlyConvert` carries two `i2l`s — one the conversion written in the source, the other the
promotion added for the second operand — and the multiplication is now done at `long` width.
One instruction more, but a separate computation.

The concatenation rows tie the same `+` operator to two separate outcomes. Concatenation
involving a variable leaves a **dynamic call** in four instructions; concatenating two
constants is two instructions and **no** added step at all. The same operator, one a call,
one zero steps.

The last two rows are short-circuit evaluation's trace in the class file. The form using `&&`
produces **10** instructions and **3** branches, the form using `&` produces **14**
instructions and **4** branches. The `&` form has to convert both comparisons to a boolean
value; `&&` puts in a branch that never enters the second comparison at all when the first
comes out false. Short circuit is not a run-time convenience, it is a **jump** sitting in the
class file.

#### The Added Conversion's Visible Result

Every step counted so far has a counterpart that changes a value.

- **BS13** — In the overflow measurement, record count is 100000, unit weight is 50000
  grams; the product is too large to hold at `int` width, and the only difference between the
  two forms is where the conversion sits.
- **BS14** — In the short-circuit measurement, the warehouse carries three records and two of
  them have a `count` field of zero; the right operand is a method call, and that method
  increments a counter every time it is called, so how many times the skipped branch was
  skipped can be counted.

```java
// Result.java — the visible result of an added conversion and a skipped branch
public class Result {
    record Item(String name, byte count, long weight) {}

    static int calls = 0;

    static boolean isHeavy(Item k) { calls++; return k.weight() > 0; }

    public static void main(String[] args) {
        byte count = 1;
        count += 300;
        System.out.println("byte count = 1; count += 300      ->  " + count);

        int stock = 100000, unit = 50000;
        System.out.println("long lateConvert(100000, 50000)   ->  " + (long) (stock * unit));
        System.out.println("long earlyConvert(100000,50000)   ->  " + (long) stock * unit);

        Item[] warehouse = { new Item("bolt", (byte) 0, 40L),
                              new Item("screw", (byte) 3, 90L),
                              new Item("nut", (byte) 0, 12L) };
        calls = 0;
        int a = 0;
        for (Item k : warehouse) if (k.count() > 0 && isHeavy(k)) a++;
        int shortCircuit = calls;
        calls = 0;
        int b = 0;
        for (Item k : warehouse) if ((k.count() > 0) & isHeavy(k)) b++;
        System.out.println("&&  result: " + a + "  isHeavy calls: " + shortCircuit);
        System.out.println("&   result: " + b + "  isHeavy calls: " + calls);
    }
}
```

```
byte count = 1; count += 300      ->  45
long lateConvert(100000, 50000)   ->  705032704
long earlyConvert(100000,50000)   ->  5000000000
&&  result: 1  isHeavy calls: 1
&   result: 1  isHeavy calls: 3
```

The first line is what the `i2b` instruction costs. 300 was added to a count, and the result
came out **45**. No conversion is written in the source, the compiler produces no warning,
and the program keeps running. The decision was made not by the programmer but by the added
step.

The second and third lines show what conversion's **location** costs. When the product is
computed at `int` width, the result is **705032704**; at `long` width, **5000000000**. It is
not enough for the left-side variable to be `long` — the result's width cannot be rescued by a
conversion done after the multiplication. This is the clearest measurement showing that
promotion applies **per operation.**

The last two lines are short circuit's run-time counterpart. Both forms find the same result:
**1** record. The number of calls paid is separate: the `&&` form runs the right operand **1**
time, the `&` form runs it **3** times. A branch-instruction difference in the class file
turns here into a two-method-call difference. If the right operand carried a side effect, the
two forms would not give the same program; the choice between `&` and `&&` is not a matter of
style.

#### The Bounding Measurement: No Added Step on Constants

Three rows in the table give the same product in three separate forms, and in all three the
added-step count is **zero.** Instruction counts are separate: **2**, **2**, and **6**.

- **BS15** — All three methods return the same number; the only difference between them is
  whether the factors are known at compile time.

`constantArithmetic` finishes in two instructions: there is **no** multiplication instruction
in the class file, the result is written directly. Constant folding, introduced in the
compilation chain, turns into something measured here. `finalConstantProduct` is also two
instructions — because `SHELVES` is a `static final` field and its initial value is known at
compile time; the compiler substitutes not the name but the **value** and does the folding.
`variableProduct` is six instructions: because `shelfCount` can change later, its value is
read and the two multiplications are done at run time.

This trio bounds the lesson's thesis. An added step is not a rule, it is a **conditional
counterpart**: a conversion is added when operand widths diverge, a narrowing is added when
the left side is narrow, a dynamic call is added when one of the operands is a variable. If
all operands are known at compile time, nothing is left to add — on the contrary, the
operation written in the source then **drops out** of the class file. An added step is
sometimes a removal.

The boundary has a practical counterpart too: declaring a constant `static final` is not just
a declaration of immutability, it grants the compiler permission to substitute that value. The
same constant declared without `final` pays two multiplications at run time. The difference in
this measurement is four instructions and is invisible by looking at the source.

#### Summary

- Numeric promotion is a conversion instruction in the class file: when an `int` and a `long`
  are added, one of five instructions is an **`i2l`** never written in the source.
- Compound assignment adds the narrowing on its own; `count += 300` leaves one **`i2b`** in
  seven instructions and makes the result **45**, and the compiler produces no warning. The
  form that writes the narrowing in the source is indistinguishable from this in the class
  file.
- Promotion does not always produce an instruction: when a `byte` and a `short` are added,
  there is no conversion at all in the class file, because both are already loaded at `int`
  width.
- Promotion applies per operation: the same product gives **705032704** at `int` width,
  **5000000000** at `long` width, and assigning the result to a `long` variable does not fix
  this.
- Short circuit is a branch instruction: the `&&` form produces **10** instructions and **3**
  branches, the `&` form **14** instructions and **4** branches; at run time the right operand
  runs **1** and **3** times.
- No added step is left when all operands are known at compile time: the same product is
  **2** instructions with constants, **6** with a variable, and zero added steps in all
  three.

#### Next Step

This lesson measured a branch instruction for the first time — one that carries the flow from
one point to another — and it came up as an operator's byproduct. The next lesson makes the
branch its main subject: the same behavior written with `if` and `switch` compiles to **two
separate instructions** in the class file, and the two instructions' lookup cost is not the
same. What a labeled jump earns in nested loops is measured in instruction count, why the
enhanced `for` loop produces separate instructions over an array versus a list is shown — and
what the gain costs in readability is written in the same place.

### Control Flow

Source: https://academia.sh/en/courses/java-fundamentals/control-flow

The same behavior compiles to two separate lookup instructions in the class file: dense labeled selection builds a table that indexes directly, sparse labeled selection builds an instruction that searches among labels; labeled jump earns both a step and the correct row against a plain break, and zero steps against the flagged form.

The previous lesson turned up a branch instruction as an operator's byproduct: short-circuit
evaluation was a jump that never entered the second operand at all. This lesson makes the
branch its main subject. Forms written separately in the source as `if`, `switch`, `for`,
`while`, `break`, and `continue` do not find this variety in the class file. There, only a
handful of instruction kinds exist: a conditional branch, an unconditional jump, and two kinds
of **selection** instruction.

The Programming Fundamentals course established conditional branching, loops, and loop
control as concepts — **those are not repeated here.** There, what a flow form does was
explained. What is measured here is which instruction the same behavior compiles to, and that
two separate instructions carry **separate lookup costs.**

#### Flow Form and Flow Instruction

Two separate instructions answer a selection in the class file, and the compiler decides
which to use by looking at **how the labels are distributed.**

- A **dense selection instruction** holds a table: one entry for every value from the
  smallest label to the largest. The given value indexes directly into the table; there is no
  search, there is a single position calculation. In return, the table also holds space for
  the gaps in between.
- A **sparse selection instruction** holds only the real labels, in order, and **searches**
  for the given value within that ordered list. It holds no space, but a search is paid on
  every selection.

There is a third path too: writing the same behavior as an `if` chain. Then there is no
selection instruction at all; as many conditional branches as there are labels are strung one
after another, and for the last label all of them are tried. This lesson measures all three
forms on the warehouse's **shelf number**, carried through the course, then moves on to loop
forms.

#### The Measurement Core

The core is the previous lessons' core; this lesson reads two measurements. The first is
**branch instruction count.** The second, when a method has a selection instruction, is
**which one it is**: for dense selection the range the table covers is printed, for sparse
selection the number of labels held is printed.

- **BS16** — Branch count is the number of conditional and unconditional jump instructions in
  the class file; not how many times they run, but how many are written is counted.
- **BS17** — A selection instruction is found at most once in a single method; when the
  column is empty, that method has no selection instruction at all.

```java
// Gauge.java — reads which instruction a flow form compiles to 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 Method(String name, int instructions, int branches, String select, List<String> added) {}

    static List<Method> read(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Files.writeString(d.resolve(className + ".java"), source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(sink, sink, "-d", d.toString(),
                d.resolve(className + ".java").toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(className + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            String select = "-";
            int instructions = 0, branches = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof BranchInstruction) branches++;
                else if (e instanceof TableSwitchInstruction t)
                    select = "dense " + t.lowValue() + ".." + t.highValue();
                else if (e instanceof LookupSwitchInstruction l)
                    select = "sparse " + l.cases().size() + " labels";
                else if (e instanceof InvokeInstruction iv)
                    added.add(iv.name().stringValue());
                else if (e instanceof TypeCheckInstruction tc && tc.opcode() == Opcode.CHECKCAST)
                    added.add("type check");
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, branches, select, added));
        }
        return methods;
    }
}
```

#### Ten Flow Forms

The measured class's first three methods give **the same form of writing** with three
separate label distributions; the fourth writes the third's mapping as an `if` chain. The
next four compute the same sum over an array and a list, with the enhanced `for` and with a
manually unrolled form. The last two show two ways of exiting two nested loops.

```java
// Flow.java — ten flow forms, which instruction in the class file
public class Flow {
    static final String SOURCE = """
        import java.util.Iterator;
        import java.util.List;
        class Warehouse {
            static int denseSelect(int shelf) {
                switch (shelf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 4: return 40; default: return 0; }
            }
            static int withGaps(int shelf) {
                switch (shelf) { case 1: return 10; case 2: return 20;
                               case 3: return 30; case 10: return 40; default: return 0; }
            }
            static int sparseSelect(int shelf) {
                switch (shelf) { case 1: return 10; case 100: return 20;
                               case 10000: return 30; case 1000000: return 40; default: return 0; }
            }
            static int ifChain(int shelf) {
                if (shelf == 1) return 10;
                if (shelf == 100) return 20;
                if (shelf == 10000) return 30;
                if (shelf == 1000000) return 40;
                return 0;
            }
            static int arraySum(int[] counts) { int t = 0; for (int x : counts) t += x; return t; }
            static int arrayIndexed(int[] counts) {
                int t = 0;
                for (int i = 0; i < counts.length; i++) t += counts[i];
                return t;
            }
            static int listSum(List<Integer> counts) { int t = 0; for (int x : counts) t += x; return t; }
            static int manualIterator(List<Integer> counts) {
                int t = 0;
                for (Iterator<Integer> y = counts.iterator(); y.hasNext(); ) { int x = y.next(); t += x; }
                return t;
            }
            static int labeled(int[][] warehouse, int wanted) {
                int row = -1;
                outer:
                for (int i = 0; i < warehouse.length; i++)
                    for (int j = 0; j < warehouse[i].length; j++)
                        if (warehouse[i][j] == wanted) { row = i; break outer; }
                return row;
            }
            static int flagged(int[][] warehouse, int wanted) {
                int row = -1;
                boolean found = false;
                for (int i = 0; i < warehouse.length && found == false; i++)
                    for (int j = 0; j < warehouse[i].length; j++)
                        if (warehouse[i][j] == wanted) { row = i; found = true; break; }
                return row;
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-16s %6s %4s %-16s %s%n",
                "method", "instr", "br", "select instr", "step not written in source");
        for (Gauge.Method y : Gauge.read(SOURCE, "Warehouse")) {
            if (y.name().startsWith("<")) continue;
            System.out.printf("%-16s %6d %4d %-16s %s%n", y.name(), y.instructions(), y.branches(), y.select(),
                    y.added().isEmpty() ? "-" : String.join(", ", y.added()));
        }
    }
}
```

```
method            instr   br select instr     step not written in source
denseSelect          12    0 dense 1..4       -
withGaps             12    0 dense 1..10      -
sparseSelect         12    0 sparse 4 labels  -
ifChain              22    4 -                -
arraySum             24    2 -                -
arrayIndexed         18    2 -                -
listSum              20    2 -                iterator, hasNext, next, type check, intValue
manualIterator       20    2 -                iterator, hasNext, next, type check, intValue
labeled              32    6 -                -
flagged              38    7 -                -
```

The first three rows are the lesson's central measurement. All three methods carry four
labels and a default branch, are written with the same form, and turn into **the same 12
instructions** in the class file. But the selection instruction is not the same: when labels
spread from 1 to 4, a **dense** instruction is produced; when they spread into the millions, a
**sparse** instruction is produced. There is no structural difference at all between the two
`switch` statements in the source; the difference is only in the numbers' values, and the
compiler chose a separate instruction by looking at those values. Instruction count does not
see this — what has to be counted here is not instruction count, it is **which instruction.**

The second row shows how the decision is made. `withGaps` carries only four labels, but its
smallest is 1, its largest is 10; the compiler still chooses the **dense** instruction and the
table stretches from **1 to 10.** So the class file also holds space for six unused entries.
The compiler's decision is a tradeoff: the gaps that fill it take up space in the file, and in
return every selection does a single index instead of a search. Once labels spread far
enough, the tradeoff flips.

The fourth row is selection's alternative. When the same mapping is written as an `if` chain,
there is no selection instruction at all: **22** instructions and **4** branches. What the
four branches mean is that to find the last label, the previous three all have to be tried
too. Selection instructions reduce this trying to a single step; an `if` chain grows with
label count. The same behavior, two separate costs.

#### The Same Loop, Two Separate Steps

The four rows in the middle show that the enhanced `for` loop is not a single form of
writing.

`arraySum` produces **24** instructions and **no** step at all unwritten in the source: the
compiler opens the enhanced `for` form into a counted loop. `listSum` finishes the same body
in **20** instructions but carries **five** added steps — an iterator is taken, queried
twice, the returned object is checked, and opened into a number. These numbers were read as
boxing's cost in the first lesson; here their reading is different. The same **form of
writing** compiles to two separate loops depending on the type of what is walked. One syntax,
two instructions.

The two rows beside them prove this. `arrayIndexed` is a hand-written counted loop and
produces **18** instructions — **six** fewer than the enhanced form, because the enhanced form
copies the array reference and the length into separate slots. `manualIterator` is a
hand-written iterator loop and comes out **exactly identical** to `listSum`: the same 20
instructions, the same two branches, the same five steps. The enhanced `for` over a list is
exactly this loop's shorthand; over an array it is a slightly more generously written form of
the counted loop.

The last two rows are the next section's subject: labeled jump is **32** instructions and
**6** branches, the flagged form is **38** instructions and **7** branches.

#### Labeled Jump's Measured Gain

Java offers three ways to exit two nested loops at once: putting a label on the outer loop and
writing `break outer`, holding a flag variable, or just writing `break` and exiting only the
inner loop. The three do **not** give the same program.

- **BS18** — The warehouse carries three rows and four records per row; the sought name is
  found in **two** rows, the first being the second row's third record. The correct answer is
  **1.**
- **BS19** — The step counter is incremented only in the inner loop's body, meaning it counts
  the number of **records compared**; no duration is measured.

```java
// Jump.java — three exit forms, how many steps and which row
public class Jump {
    record Item(String name, int count, long weight) {}

    static int steps = 0;

    static Item[][] warehouse() {
        return new Item[][] {
            { new Item("bolt", 4, 40L), new Item("washer", 9, 5L),
              new Item("nut", 2, 12L), new Item("pin", 1, 300L) },
            { new Item("gasket", 7, 8L), new Item("spring", 3, 15L),
              new Item("screw", 5, 90L), new Item("ball", 6, 20L) },
            { new Item("screw", 2, 90L), new Item("wedge", 8, 30L),
              new Item("bushing", 4, 60L), new Item("bearing", 1, 250L) },
        };
    }

    static int labeled(Item[][] d, String wanted) {
        int row = -1;
        outer:
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                steps++;
                if (d[i][j].name().equals(wanted)) { row = i; break outer; }
            }
        return row;
    }

    static int flagged(Item[][] d, String wanted) {
        int row = -1;
        boolean found = false;
        for (int i = 0; i < d.length && found == false; i++)
            for (int j = 0; j < d[i].length; j++) {
                steps++;
                if (d[i][j].name().equals(wanted)) { row = i; found = true; break; }
            }
        return row;
    }

    static int plainBreak(Item[][] d, String wanted) {
        int row = -1;
        for (int i = 0; i < d.length; i++)
            for (int j = 0; j < d[i].length; j++) {
                steps++;
                if (d[i][j].name().equals(wanted)) { row = i; break; }
            }
        return row;
    }

    public static void main(String[] args) {
        Item[][] d = warehouse();
        System.out.printf("%-12s %6s %8s%n", "form", "row", "steps");
        steps = 0;
        System.out.printf("%-12s %6d %8d%n", "labeled", labeled(d, "screw"), steps);
        steps = 0;
        System.out.printf("%-12s %6d %8d%n", "flagged", flagged(d, "screw"), steps);
        steps = 0;
        System.out.printf("%-12s %6d %8d%n", "plain break", plainBreak(d, "screw"), steps);
    }
}
```

```
form            row    steps
labeled           1        7
flagged           1        7
plain break       2        8
```

The third row is the measurement's most important part, and it is not a performance
difference. A plain `break` exits only the **inner** loop; the outer loop keeps going from
where it was, and after the match on the second row is found, the third row is scanned too and
the `row` variable is **overwritten.** The result is **2**, that is, wrong. One extra step was
paid, and that step produced the wrong answer. In nested loops, which loop a `break` belongs
to is not a matter of style.

The first two rows, by contrast, bound labeled jump's real gain. The labeled form and the
flagged form find **the same row** and pay **the same 7 steps.** Labeled jump's run-time gain
against the flagged form is **zero.**

#### The Bounding Measurement: Where the Gain Is, Where the Cost Is

Labeled jump's measurable gain sits in two places, and both are small.

- **BS20** — The comparison is the `labeled` and `flagged` methods in the class-file
  measurement; both carry the same signature and return the same result.

The first is in the class file: the labeled form produces **32** instructions and **6**
branches, the flagged form **38** instructions and **7** branches — six instructions and one
branch fewer. The flag holds a slot, is read at the start of every outer round, and is
written on a match; all of this is instructions. The second is in the source: in the flagged
form, the exit condition is spread across **two places** — the outer loop's header and the
inner loop's body. In the labeled form, the exit is on a single line.

The cost has to be written too. A label sends the reader's eye **backward** in the source:
someone reading the `break outer` line has to look up to find which loop the `outer` label
sits on. In two loops this look is short; at three or four levels, and with a `continue` in
between too, labels make the flow hard to follow. The measurement does not decide, it only
says how much of what: the gain is **six instructions and one slot**, it is **zero** as a
run-time step, and the cost is the reader having to look up.

This boundary also completes the lesson's thesis. The choice between flow forms mostly
changes instruction count but does not change the step paid. The one place that genuinely
changes is where the form makes **incorrect behavior** possible — as with the plain `break`
line.

#### Summary

- The same selection compiles to two separate instructions depending on label distribution: a
  table that indexes directly under dense distribution, an instruction that searches among
  labels under sparse distribution. Instruction count is **12** in all three; what changes is
  which instruction it is.
- The dense instruction's table stretches from the smallest label to the largest: a
  four-label selection can hold space from **1 to 10.** The decision is a tradeoff, file size
  against search.
- When the same mapping is written as an `if` chain, there is no selection instruction at
  all: **22** instructions and **4** branches, and for the last label all of them are tried.
- The enhanced `for` is not a single form of writing: it produces **24** instructions and
  **0** added steps over an array, **20** instructions and **5** added steps over a list; the
  list form is exactly identical to a hand-written iterator loop.
- Labeled jump earns both a step and the **correct row** against a plain `break`; against the
  flagged form its gain is **six instructions**, zero as a run-time step, and its cost is the
  reader having to look up for the label.

#### Next Step

This lesson measured which instruction flow forms compile to: the same selection turned into
two separate lookup instructions, the same enhanced `for` loop turned into separate steps over
an array and a list. All these measurements walked **over** something, and that thing itself
was never questioned. What is left is the thing walked over. The next lesson measures the
array: how many instructions the line `new int[3]` leaves in the class file, whether the form
`d.length` is a field read or an instruction of its own, and where in the source the step that
checks an array's bound sits.

### Arrays

Source: https://academia.sh/en/courses/java-fundamentals/arrays

An array in Java is an object, and the measurement shows this in three places: production leaves a single instruction, d.length is not a field read but an instruction of its own, and bound checking and array covariance's type check are left to run time without ever showing up in the class file.

The previous lesson measured which instruction flow forms compile to: the same selection
turned into two separate lookup instructions, the same enhanced `for` loop turned into
separate steps over an array and a list. What is left is the thing walked over itself.

The array was established in the Data Structures course as a data structure: fixed size,
contiguous layout, arithmetic that goes from a position to an address. This lesson does not
repeat that. The question here is: how many instructions does the line `new int[3]` leave in
the class file, is the form `d.length` a field read, and where in the source does the step
that checks the array's bound sit? All three answers come from the array being an **object**
in Java.

#### An Array Is an Object

- **BS21** — The measured source text is written inside the lesson. Since we know what we
  wrote, every extra found in the class file is what the compiler added.
- **BS22** — The `Gauge` core comes from the shared definition. This lesson adds only one
  measurement to it: `instructionNames`, which returns a method's instruction names in
  order. The behavior of the existing measurements does not change.
- **BS23** — Instruction names are the names in the class file and are printed lowercased
  with `Locale.ROOT`; locale does not affect the result.
- **BS24** — If the compiler returns a nonzero result, the core throws an exception. The
  lesson catches this exception and prints only the "did not compile" line; the compiler's
  message text is not printed.

```java
// Gauge.java — shared measurement core: reads what the compiler puts 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 Method(String name, int instructions, List<String> added) {}
    record ClassInfo(List<Method> methods, List<String> fields) {}

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

    static ClassInfo read(String source, String className) throws Exception {
        ClassModel cm = compile(source, className);
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof InvokeInstruction iv)
                    added.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    added.add("new array " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    added.add("new array " + shortName(a.componentType().asInternalName()));
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, added));
        }
        List<String> fields = new ArrayList<>();
        for (FieldModel f : cm.fields()) fields.add(f.fieldName().stringValue());
        return new ClassInfo(methods, fields);
    }

    static List<String> instructionNames(String source, String className, String method) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(method))
                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 internal) { return internal.substring(internal.lastIndexOf('/') + 1); }
}
```

```java
// ArrayDemo.java — what array production leaves in the class file
import java.util.Arrays;

class Item {
    String name;
    int count;
    long weight;
}

public class ArrayDemo {
    static final String K = """
        class ArraySample {
            static class Item { String name; int count; long weight; }
            static int[] empty() { return new int[3]; }
            static Item[] items() { return new Item[3]; }
            static int[] withConstants() { return new int[] {12, 18, 7}; }
            static int length(int[] d) { return d.length; }
            static int count(Item k) { return k.count; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-10s %6s  %s%n", "method", "instr", "step not written in source");
        for (Gauge.Method y : Gauge.read(K, "ArraySample").methods())
            if (!y.name().equals("<init>"))
                System.out.printf("%-10s %6d  %s%n", y.name(), y.instructions(),
                        y.added().isEmpty() ? "-" : String.join(", ", y.added()));

        System.out.println();
        System.out.println("d.length read: " + Gauge.instructionNames(K, "ArraySample", "length"));
        System.out.println("k.count read:  " + Gauge.instructionNames(K, "ArraySample", "count"));

        int[] numbers = new int[3];
        Item[] items = new Item[3];
        System.out.println();
        System.out.println("int[] class:       " + numbers.getClass().getName());
        System.out.println("Item[] class:      " + items.getClass().getName());
        System.out.println("array's superclass: " + numbers.getClass().getSuperclass().getName());
        System.out.println("initial values: " + Arrays.toString(numbers)
                + " " + Arrays.toString(items));

        try {
            Gauge.read(K.replace("return d.length;", "d.length = 5; return 0;"), "ArraySample");
        } catch (IllegalStateException e) {
            System.out.println("d.length = 5   -> " + e.getMessage());
        }
    }
}
```

```
method      instr  step not written in source
empty           3  new array int
items           3  new array ArraySample$Item
withConstants     15  new array int
length          3  -
count           3  -

d.length read: [aload_0, arraylength, ireturn]
k.count read:  [aload_0, getfield, ireturn]

int[] class:       [I
Item[] class:      [LItem;
array's superclass: java.lang.Object
initial values: [0, 0, 0] [null, null, null]
d.length = 5   -> did not compile: ArraySample
```

The first two rows say array production is **three instructions**: load the size, produce
the array, return. The gauge records this production as `new array`, because in the class
file array production falls into the same class as object production. In the `Item[3]` row's
record, the class's **full name** stands; the element type is written into the array.

The third row makes a difference visible. The `withConstants` method, which writes three
values inside square brackets, produces **15** instructions; the empty array of the same size
produces **3.** The twelve instructions in between are the three elements being written one
by one: for every element, duplicate the array, load the position, load the value, write it.
The form `{12, 18, 7}`, which stands like a single constant in the source, has no constant
counterpart in the class file. Constant folding, measured in the first lesson, works on
numbers; it does not work on an array, because what would need folding is not a value, it is
an object produced at run time.

The last three rows put this into a name. The array has a class — `[I` for `int[]`, `[LItem;`
for `Item[]` — and that class's superclass is `java.lang.Object`. An array is an object: it
is produced on the heap, held by a reference, and passes into any spot expecting an `Object`.
Production does not produce elements; a number array starts with zeros, a record array with
`null`. Building a record array does not build three records, it builds three empty spots.

The length rows give the distinction's second half. The form `d.length` compiles to the
`arraylength` instruction, the form `k.count` to `getfield`. The syntax looks the same in
both — a dot and a name — but their counterparts are separate instructions. `length` is not a
field; it is not in the field list in the class file, it has its own instruction. This has a
visible consequence in the last line: source writing `d.length = 5` **does not compile.**
Growing an array is impossible not because of a prohibition, but because no instruction exists
to be its counterpart.

#### The Bound-Checking Step Is Not in the Source

- **BS25** — In the bound measurement, only the exception's **class name** is printed. The
  exception message contains the position and the length; that text is not printed, because
  what is measured is the check's existence.

```java
// AccessDemo.java — which instruction bound checking sits inside
public class AccessDemo {
    static final String K = """
        class AccessSample {
            static int read(int[] d, int i) { return d[i]; }
            static void write(int[] d, int i, int v) { d[i] = v; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("d[i] read:  " + Gauge.instructionNames(K, "AccessSample", "read"));
        System.out.println("d[i] write: " + Gauge.instructionNames(K, "AccessSample", "write"));

        int[] d = {12, 18, 7};
        System.out.println();
        for (int i : new int[] {0, 2, 3, -1}) {
            try {
                System.out.printf("d[%d] -> %d%n", i, d[i]);
            } catch (RuntimeException e) {
                System.out.printf("d[%d] -> %s%n", i, e.getClass().getSimpleName());
            }
        }
        int size = -1;
        try {
            System.out.println("new int[size] -> " + new int[size].length);
        } catch (RuntimeException e) {
            System.out.println("new int[size] -> " + e.getClass().getSimpleName() + " (size = " + size + ")");
        }
    }
}
```

```
d[i] read:  [aload_0, iload_1, iaload, ireturn]
d[i] write: [aload_0, iload_1, iload_2, iastore, return]

d[0] -> 12
d[2] -> 7
d[3] -> ArrayIndexOutOfBoundsException
d[-1] -> ArrayIndexOutOfBoundsException
new int[size] -> NegativeArraySizeException (size = -1)
```

Reading is four instructions, writing is five. In neither is there a comparison, a branch, or
a call: load the array, load the position, read or write. Bound checking is not written in
the source, and it does not stand as **a separate instruction** in the class file either. The
check sits inside the **definition** of `iaload` and `iastore`; the virtual machine does it on
every access, and if the position is out of range, it throws an exception.

This is the added step's third form. The first is an instruction placed in the class file —
conversion, call, production. The second is a member never written in the source at all. The
third is embedded in the instruction's own definition and **never changes instruction count
at all**; the gauge cannot count it, it can only be read from its run-time result. Of the
measurement's four accesses, two give a value, two give an exception; the negative position is
not a separate rule, it is the same rule's result.

The last line shows the check's second location: a production request with a negative size
also falls at run time and gives a separate exception, because the size does not have to be
known at compile time.

#### A Multidimensional Array Is an Array of Arrays

- **BS26** — Row lengths are written one by one in the measurement; none is produced
  randomly, none is read from the environment.

```java
// MultiDim.java — a multidimensional array is an array of arrays
public class MultiDim {
    static final String K = """
        class DimSample {
            static int[][] regular() { return new int[2][3]; }
            static int[][] jagged() { return new int[2][]; }
            static int rowLength(int[][] d, int i) { return d[i].length; }
        }
        """;

    public static void main(String[] args) throws Exception {
        for (String y : new String[] {"regular", "jagged", "rowLength"})
            System.out.printf("%-14s %s%n", y, Gauge.instructionNames(K, "DimSample", y));

        int[][] t = new int[3][];
        t[0] = new int[] {12};
        t[1] = new int[] {18, 7, 4};
        t[2] = new int[0];
        System.out.println();
        System.out.println("outer array class: " + t.getClass().getName());
        System.out.println("one row's class:   " + t[0].getClass().getName());
        System.out.printf("outer length %d, row lengths %d %d %d%n",
                t.length, t[0].length, t[1].length, t[2].length);

        int[][] d = new int[2][3];
        System.out.println("in a regular array, are rows the same object: " + (d[0] == d[1]));
        d[0][0] = 12;
        System.out.println("after d[0][0] = 12, d[1][0]: " + d[1][0]);
    }
}
```

```
regular        [iconst_2, iconst_3, multianewarray, areturn]
jagged         [iconst_2, anewarray, areturn]
rowLength      [aload_0, iload_1, aaload, arraylength, ireturn]

outer array class: [[I
one row's class:   [I
outer length 3, row lengths 1 3 0
in a regular array, are rows the same object: false
after d[0][0] = 12, d[1][0]: 0
```

The third row writes the lesson's sentence: reading `d[i].length` is **two** steps. First the
row is loaded with `aaload` — because a row is a reference, an object — then that object's
length is read with `arraylength`. A two-dimensional array is not a single contiguous block;
the outer array's elements are references to inner arrays. The class name writes this too:
the outer array's class is `[[I`, a row's is `[I`.

The first two rows separate two different productions. `new int[2][3]`, which gives every
dimension, is a single `multianewarray` instruction and produces the rows along with the
outer array. `new int[2][]`, which gives only the outer dimension, is an `anewarray`
instruction; the outer array is produced, its rows stay `null`. The second lets rows be built
at **separate lengths**: the three rows in the measurement have lengths 1, 3, and 0. The last
two lines confirm that even in a regular array, rows are separate objects — `d[0]` and `d[1]`
are not the same object, and writing to the first row does not change the second. Java has no
separate multidimensional array type.

#### One Type Check Per Write

- **BS27** — The same write is tried on three separate sources. Two compile and their
  instructions are printed, one does not compile and passes with the "did not compile" line;
  which one falls at which time is the measurement itself.

```java
// Covariance.java — array covariance: which write compiles, which write falls
public class Covariance {
    static final String STRING_ARR = "class CovSample { static void write(String[] d) { d[0] = %s; } }";
    static final String OBJECT_ARR = "class CovSample { static void write(Object[] d) { d[0] = %s; } }";

    static void tryIt(String name, String source) {
        try {
            System.out.printf("%-28s -> %s%n", name, Gauge.instructionNames(source, "CovSample", "write"));
        } catch (Exception e) {
            System.out.printf("%-28s -> %s%n", name, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        tryIt("String[] d; d[0] = \"north\"", STRING_ARR.formatted("\"north\""));
        tryIt("String[] d; d[0] = 12", STRING_ARR.formatted("12"));
        tryIt("Object[] d; d[0] = 12", OBJECT_ARR.formatted("12"));

        String[] names = new String[2];
        Object[] view = names;
        System.out.println();
        System.out.println("Object[] view = names;  same object: " + (view == names));
        System.out.println("view's class: " + view.getClass().getName());
        view[0] = "north";
        System.out.println("view[0] = \"north\" -> names[0] = " + names[0]);
        try {
            view[1] = 12;
            System.out.println("view[1] = 12 -> " + names[1]);
        } catch (RuntimeException e) {
            System.out.println("view[1] = 12 -> " + e.getClass().getSimpleName()
                    + ", names[1] = " + names[1]);
        }
        int[] numbers = new int[2];
        numbers[1] = 12;
        System.out.println("same write on the int[] side -> " + numbers[1]);
    }
}
```

```
String[] d; d[0] = "north"   -> [aload_0, iconst_0, ldc, aastore, return]
String[] d; d[0] = 12        -> did not compile: CovSample
Object[] d; d[0] = 12        -> [aload_0, iconst_0, bipush, invokestatic, aastore, return]

Object[] view = names;  same object: true
view's class: [Ljava.lang.String;
view[0] = "north" -> names[0] = north
view[1] = 12 -> ArrayStoreException, names[1] = null
same write on the int[] side -> 12
```

Arrays in Java are **covariant** (array covariance): if the element type is beneath a
supertype, the array too substitutes for an array of that supertype. `String[]` substitutes
for an `Object[]`, and the measurement's fourth line confirms this — the two names hold the
same object, its class is still `[Ljava.lang.String;`.

The first three lines write covariance's cost. Source writing a number into an array named
`String[]` **does not compile**: the compiler looks at the declared type and gives the error
at compile time. When the same array is named `Object[]`, **the same write compiles** — with
a call added that converts the number to a wrapper, too. The write is valid at the syntax
level, because any object can be written into an `Object[]`.

Run time makes the decision: the string write passes and is visible to the caller, the number
write falls with `ArrayStoreException` and `names[1]` is **still `null`** — the write was not
done, and it was not left half-done either. As the virtual machine executes the `aastore`
instruction, it checks whether the value matches the array's **real** element type; if it does
not, it does not write.

The bounding measurement here runs two ways. First: this check leaves no trace at all in
instruction count. `aastore` is a single instruction and `iastore` is a single instruction
too; looking at the class file, we cannot count the check's existence. Second: in the last
line's `int[]` write, no such check **exists.** If the element type is primitive, no question
of array covariance arises, because an array with a primitive element type never substitutes
for another array type. The added step disappears here — and where it disappears is exactly
where the subtype relationship ends.

#### Summary

- An array is an object: it has its own class, its superclass is `java.lang.Object`, and its
  production is **3** instructions in the class file; the bracketed form `{12, 18, 7}` is
  **15** instructions, because elements are written one by one. Production does not produce
  elements: a number array starts with zeros, a reference array with `null` values.
- `d.length` is not a field read; it compiles to the `arraylength` instruction, `k.count` to
  `getfield`. Source that writes to the length does not compile.
- Bound checking sits neither in the source nor in a separate instruction; it is inside the
  access instruction's own definition, does not change instruction count, and throws an
  exception on an out-of-range access.
- `int[][]` is not a separate type, it is an array of `int[]`: `d[i].length` is two
  instructions, rows are separate objects, and they can be separate lengths.
- Because of array covariance, writing a number into an array named `Object[]` that is really
  a string array compiles but falls at run time with `ArrayStoreException`; the same check
  does not exist at all for a primitive-typed array.

#### Next Step

This lesson measured that writing to a `String[]` array is checked at run time; the value
written was, every time, an already-ready string object. The next lesson looks at that object
itself: where does a string literal sit in the class file, how many objects does writing the
same literal twice produce, and how many objects does text concatenated inside a loop leave
along the way? A string's immutability, like an array's fixed size, decides steps invisible in
the source.

### Strings

Source: https://academia.sh/en/courses/java-fundamentals/strings

The same + operator gives two separate results on strings: between two constants it leaves no trace at all in the class file, between variables it turns into a dynamic call; in a loop, the 24-instruction form leaves 20 string objects while the 27-instruction builder produces a single object.

The previous lesson measured that writing to a `String[]` array is checked at run time; the
value written was, every time, an already-ready string object. This lesson looks at that
object itself.

That a string is immutable was established in the Programming Fundamentals course and is not
repeated here. The question here is: how much does immutability hold up? What step does the
`+` operator written in the source turn into in the class file, how many objects does writing
the same constant twice produce, and how many objects does text grown inside a loop leave
along the way?

#### The Concatenation Operator Does Not Stay in the Class File

- **BS28** — The measured source text is written inside the lesson; every extra found in the
  class file is what the compiler added.
- **BS29** — The `Gauge` core comes from the shared definition. This lesson writes only the
  three records it uses: call, dynamic call, and object production. The measurements'
  behavior does not change.
- **BS30** — Four of the six measured methods are four separate forms of concatenation:
  between two constants, with one variable, with three operators, and across a loop. The
  remaining two are for comparison.

```java
// Gauge.java — shared measurement core: reads what the compiler puts 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 Method(String name, int instructions, List<String> added) {}
    record ClassInfo(List<Method> methods, List<String> fields) {}

    static ClassInfo read(String source, String className) throws Exception {
        Path d = Files.createTempDirectory("gauge");
        Path k = d.resolve(className + ".java");
        Files.writeString(k, source);
        PrintWriter sink = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(sink, sink, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("did not compile: " + className);
        ClassModel cm = ClassFile.of().parse(d.resolve(className + ".class"));
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof InvokeInstruction iv)
                    added.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    added.add("dynamic:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    added.add("new " + shortName(n.className().asInternalName()));
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, added));
        }
        List<String> fields = new ArrayList<>();
        for (FieldModel f : cm.fields()) fields.add(f.fieldName().stringValue());
        return new ClassInfo(methods, fields);
    }

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

```java
// Concat.java — what string concatenation turns into in the class file
public class Concat {
    static final String K = """
        class StringSample {
            static String concat(String name, int count) { return name + count; }
            static String constantConcat() { return "si" + "mple"; }
            static int length(String s) { return s.length(); }
            static String line(String name, int count, long weight) {
                return name + " x" + count + " (" + weight + " g)";
            }
            static String concatInLoop(String[] d) {
                String s = "";
                for (String x : d) s += x;
                return s;
            }
            static String withBuilder(String[] d) {
                StringBuilder b = new StringBuilder();
                for (String x : d) b.append(x);
                return b.toString();
            }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-18s %6s  %s%n", "method", "instr", "step not written in source");
        for (Gauge.Method y : Gauge.read(K, "StringSample").methods())
            if (!y.name().equals("<init>"))
                System.out.printf("%-18s %6d  %s%n", y.name(), y.instructions(),
                        y.added().isEmpty() ? "-" : String.join(", ", y.added()));

        String name = "north";
        int count = 12;
        long weight = 1800;
        System.out.println("\ntext line's form produces: "
                + name + " x" + count + " (" + weight + " g)");
    }
}
```

```
method              instr  step not written in source
concat                  4  dynamic:makeConcatWithConstants
constantConcat          2  -
length                  3  String.length
line                    5  dynamic:makeConcatWithConstants
concatInLoop           24  dynamic:makeConcatWithConstants
withBuilder            27  new StringBuilder, StringBuilder.<init>, StringBuilder.append, StringBuilder.toString

text line's form produces: north x12 (1800 g)
```

The first row is the lesson's central measurement. In the source, `name + count` is written
and an operator sits there; in the class file there is a **dynamic call.** String
concatenation is not found in the class file as an operator — because no concatenation
instruction exists as its counterpart. The compiler turns the operator into a call; how the
call is realized is left to run time, and only the operation's name along with the constant
parts written in the source stand in the class file. Someone looking at the source cannot see
that a number is being converted to text either: `count` is an integer, and that conversion is
inside the same call.

The second row drops the same operator to zero. The method writing `"si" + "mple"` is **2**
instructions: load the constant, return. There is no concatenation there at all. Two string
constants have been folded into a single constant during compilation — this is the string
counterpart of the constant folding measured in the first lesson. The same `+` operator, a
call on one line, nothing on the other.

The third row places the difference from the previous lesson. On an array, `d.length` was an
instruction; on a string, `s.length()` is a **call**, and the gauge records it as an added
step. The syntax says this too: there are no parentheses on the array, there are on the
string. Length is read on the array from the object's header, on the string it is asked for
with a method call; because both are written with the same word they get confused, in the
class file they resemble each other not at all.

The fourth row measures the operator's most-used form. In the `line` method that builds a
record line, there are **three** `+` operators, but in the class file there stands **a
single** dynamic call, and the whole method is **5** instructions. The compiler does not
compile consecutive concatenations one by one; it gathers all the pieces into a single call.
Saying "every `+` produces an intermediate text" by looking at the source would be wrong for
this reason — the number of concatenations inside an expression is not the number of
intermediate objects produced. The line below shows the text this form produces: the record's
name, count, and weight in grams combine in a single call.

The bottom two rows set up the second pair. The method that writes `s += x` inside a loop is
**24** instructions, the one written with a builder is **27** instructions. Someone looking
at instruction count would pick the first. The added-step list, though, shows the opposite
direction: the first has a single dynamic call running on every turn of the loop, the second
has one object production and three calls. The numbers give no reason for a choice here;
instruction count does not say how many times the program does what.

#### Objects Left in the Loop

- **BS31** — The measurement does not measure duration; it counts **string objects
  produced.** On every turn, whether the result is the same as the previous object is asked
  with `==`, and what comes out separate is counted. No identity number is printed.
- **BS32** — Part count is tried with three separate values; what is measured is not a single
  number but how that number changes with part count.

```java
// Production.java — how many string objects are left in the loop
public class Production {
    static String[] parts(int n) {
        String[] d = new String[n];
        for (int i = 0; i < n; i++) d[i] = (i % 2 == 0) ? "north" : "-";
        return d;
    }

    static int withOperator(String[] d) {
        String s = "";
        int produced = 0;
        for (String x : d) {
            String previous = s;
            s = s + x;
            if (s != previous) produced++;
        }
        return produced;
    }

    static boolean builderSame(String[] d) {
        StringBuilder b = new StringBuilder();
        boolean same = true;
        for (String x : d) same = same && (b.append(x) == b);
        return same;
    }

    public static void main(String[] args) {
        System.out.printf("%6s %22s %28s%n", "parts", "strings by operator",
                "append returned same object");
        for (int n : new int[] {5, 10, 20})
            System.out.printf("%6d %22d %28s%n", n, withOperator(parts(n)),
                    builderSame(parts(n)));

        StringBuilder b = new StringBuilder();
        for (String x : parts(5)) b.append(x);
        System.out.println("\ntwo toString calls same object: " + (b.toString() == b.toString()));
        String withOperators = "";
        for (String x : parts(5)) withOperators += x;
        System.out.println("do both paths give the same text: " + withOperators.equals(b.toString()));
        System.out.println("result: " + b);
    }
}
```

```
 parts    strings by operator  append returned same object
     5                      5                         true
    10                     10                         true
    20                     20                         true

two toString calls same object: false
do both paths give the same text: true
result: north-north-north
```

The number moves exactly with part count: 5 parts, 5 objects; 20 parts, 20 objects. Only the
last of these is ever used; the other nineteen are built and discarded. This is not an
implementation detail, it is immutability's direct consequence. The form `s += x` cannot make
an addition to the `s` object, because writing onto a string is not an operation that exists;
the only thing it can do is build a new string and bind the name to it. Like the array's fixed
size in the previous lesson, this is not a prohibition either, it is the absence of an
operation with a counterpart.

The `+=` form here is a shorthand too, and the shorthand's expansion changes by type. On a
number, `+=` adds a silent narrowing conversion and changes the value in place; on a string,
it adds a dynamic call, builds a new object, and **rebinds** the name to it. The two forms
look the same, the two added steps are separate, and neither is written in the source.

The third column says why the builder separates: the `append` call returns **the object it
was called on**, in all twenty of the twenty turns. A builder holds a mutable buffer;
appending writes into it and no new object is built. A string is only produced when
`toString` is called, and that call is made once. The line below confirms this call really is
a production: two consecutive `toString` calls give **separate** objects.

This completes the two paths' table. The operator path is 24 instructions and **n** objects,
the builder path is 27 instructions and **1** object; both give the same text. The course's
second thesis takes its full shape here: instruction count and work done do not move in the
same direction. Instruction count in the class file measures how much a method **is
written**, not how many times it runs. A single instruction inside a loop does more work than
three instructions outside one.

A direct rule follows from this, and it can be applied just by looking at the form of
writing: if concatenation count is **known at compile time**, the operator is enough, because
it compiles to a single call; if the count is decided at run time, the builder is used.

#### A Constant Is Not Produced, It Is Taken from the Pool

- **BS33** — In the identity measurement, only `==` and `equals` results are printed; no
  identity value or address is written.
- **BS34** — All five compared strings carry the same letters; the only thing measured is
  whether they are the **same object.**

```java
// Pool.java — a string constant is not produced, it is taken from the pool
public class Pool {
    static final String K = """
        class PoolSample {
            static String first() { return "north"; }
            static String second() { return "north"; }
            static String constructed() { return new String("north"); }
        }
        """;

    static void report(String name, String a, String b) {
        System.out.printf("%-32s %7s %8s%n", name, a == b, a.equals(b));
    }

    public static void main(String[] args) throws Exception {
        System.out.printf("%-14s %6s  %s%n", "method", "instr", "step not written in source");
        for (Gauge.Method y : Gauge.read(K, "PoolSample").methods())
            if (!y.name().equals("<init>"))
                System.out.printf("%-14s %6d  %s%n", y.name(), y.instructions(),
                        y.added().isEmpty() ? "-" : String.join(", ", y.added()));

        String constant = "north";
        String second = "north";
        String constantSum = "nor" + "th";
        String computed = "nor" + constant.substring(3);
        String constructed = new String("north");

        System.out.printf("%n%-32s %7s %8s%n", "compared with \"north\"", "==", "equals");
        report("constant written a second time", second, constant);
        report("\"nor\" + \"th\"", constantSum, constant);
        report("concatenated at run time", computed, constant);
        report("new String(\"north\")", constructed, constant);
        report("computed.intern()", computed.intern(), constant);
    }
}
```

```
method          instr  step not written in source
first               2  -
second              2  -
constructed         5  new String, String.<init>

compared with "north"                 ==   equals
constant written a second time      true     true
"nor" + "th"                        true     true
concatenated at run time           false     true
new String("north")                false     true
computed.intern()                   true     true
```

The top table is the lesson's bounding measurement. A method returning a string constant is
**2** instructions and the added-step column is empty: no production, no call, no conversion.
The constant sits ready-made in the class file and is **not produced** at run time, it is
merely loaded. Writing the same constant a second time does not build a second object
either. `new String("north")`, by contrast, is **5** instructions and adds two steps: an
object production and a constructor call. The only difference written in the source is a
single keyword; the difference in the class file is three instructions and one object.

The bottom table gives its result on identity. A constant written twice is the **same**
object; concatenating two constants is the same object too, because that concatenation was
done at compile time and the result became a constant again. When the same letters are
concatenated at run time, though, the result is a **separate** object, and `new String` gives
a separate object on every call. `equals` is correct in all five of the five rows: the content
is always the same, the identity is not.

The place that gathers all of this is the **string pool**: constants are held in a single
pool, and the same character sequence is stored once. The last line shows the way to enter
the pool afterward — an `intern` call gives the pool's counterpart of a string built at run
time, and the result comes out the same object as the constant. The only practical consequence
of this is: strings are not compared with `==`. The operator sometimes gives the correct
answer, and precisely for that reason it cannot be trusted; what gives the correct answer is
not the content, it is where the constant came from.

The `intern` call may look like a way to fix this table, but what it fixes is not the
measurement itself: every entry into the pool asks for a search and a store, and this lesson
did not count those steps. Using `equals` for comparison requires both fewer assumptions and
fewer steps; `intern` is an option worth measuring only when the same text is held many times
over and gathering it into a single copy is wanted.

#### Summary

- String concatenation is not an operator in the class file: `+` between variables turns
  into a dynamic call, the conversion from number to text is inside that same call, and three
  operators in one expression still give a **single** call.
- On an array, `d.length` is an instruction; on a string, `s.length()` is a call. The same
  word compiles to separate steps on the two types.
- Concatenating two string constants adds **no** step at all: the method is 2 instructions,
  the constant has been folded at compile time.
- Text grown with `+=` in a loop leaves one string per part — 20 parts, 20 objects — because
  writing onto a string is not an operation that exists; with a builder, `append` returns the
  object it was called on and the string is produced only once, with `toString`.
- The operator path is **24**, the builder path is **27** instructions: fewer instructions
  does not mean less work, because instruction count does not say how many times something
  runs.
- Of five strings carrying the same letters, three are the same object as the constant, two
  are separate; `equals` is correct in all five, which is why strings are not compared with
  `==`.

#### Next Step

This lesson measured that a string can come out the same by `==` and equal by `equals`; two
names sometimes held a single object. The same question can be asked of a method call too:
when a record object is given to a method, what does the called method access, and why do
changing that object's field and rebinding the parameter give separate results? The next
lesson reads Java's single passing model from the class file and closes the course.

### Value and Reference Passing

Source: https://academia.sh/en/courses/java-fundamentals/value-and-reference-passing

Java has a single passing model, and the class file writes it down: five arguments of five separate types are loaded the same way, writing to a parameter's field is visible to the caller, rebinding the parameter is not, and on an immutable type the two situations cannot be told apart.

The previous lesson measured that a string can come out the same by `==` and equal by
`equals`; two names sometimes held a single object. The same question can be asked at a
call's boundary too.

Pass by value and pass by reference were established as concepts in the Programming
Fundamentals course and are not repeated here. The question here is: which of these concepts
does Java realize, and how do we read this from the class file? Someone looking at the source
cannot see, when a record is given to a method, whether the object passes or the reference
leading to it does; the class file can.

#### Arguments Are Loaded in a Single Form

- **BS35** — The measured source is written inside the lesson; every extra found in the
  class file is what the compiler added.
- **BS36** — Two measurements from the `Gauge` core are used in this lesson: instruction
  count and added steps per method, and a method's instruction-name sequence.
- **BS37** — The five parameters in the call measurement are deliberately separate types: two
  primitive types (one occupying two slots), a class, an array, and an immutable class.

```java
// Gauge.java — shared measurement core: reads what the compiler puts 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 Method(String name, int instructions, List<String> added) {}

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

    static List<Method> read(String source, String className) throws Exception {
        List<Method> methods = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> added = new ArrayList<>();
            int instructions = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) instructions++;
                if (e instanceof InvokeInstruction iv)
                    added.add(shortName(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof InvokeDynamicInstruction id)
                    added.add("dynamic:" + id.name().stringValue());
                else if (e instanceof NewObjectInstruction n)
                    added.add("new " + shortName(n.className().asInternalName()));
            }
            methods.add(new Method(m.methodName().stringValue(), instructions, added));
        }
        return methods;
    }

    static List<String> instructionNames(String source, String className, String method) throws Exception {
        List<String> names = new ArrayList<>();
        for (MethodModel m : compile(source, className).methods())
            if (m.methodName().stringValue().equals(method))
                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 internal) { return internal.substring(internal.lastIndexOf('/') + 1); }
}
```

```java
// Passing.java — argument loading and writing to a parameter in the class file
public class Passing {
    static final String K = """
        class PassingSample {
            static class Item { String name; int count; long weight; }
            static void takeNumber(int count) {}
            static void takeWeight(long weight) {}
            static void takeItem(Item k) {}
            static void takeArray(int[] d) {}
            static void takeText(String s) {}
            static void call(int count, long weight, Item k, int[] d, String s) {
                takeNumber(count); takeWeight(weight); takeItem(k); takeArray(d); takeText(s);
            }
            static void changeField(Item k) { k.count = 99; }
            static void rebind(Item k) { k = new Item(); k.count = 99; }
            static void incrementNumber(int count) { count = count + 1; }
            static void extendText(String s) { s = s + " x"; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("call: " + Gauge.instructionNames(K, "PassingSample", "call"));
        System.out.println();
        System.out.printf("%-16s %6s  %s%n", "method", "instr", "step not written in source");
        for (Gauge.Method y : Gauge.read(K, "PassingSample"))
            if (y.name().equals("changeField") || y.name().equals("rebind")
                    || y.name().equals("incrementNumber") || y.name().equals("extendText"))
                System.out.printf("%-16s %6d  %s%n", y.name(), y.instructions(),
                        y.added().isEmpty() ? "-" : String.join(", ", y.added()));
        for (String y : new String[] {"changeField", "rebind"})
            System.out.printf("%-16s %s%n", y, Gauge.instructionNames(K, "PassingSample", y));
    }
}
```

```
call: [iload_0, invokestatic, lload_1, invokestatic, aload_3, invokestatic, aload, invokestatic, aload, invokestatic, return]

method            instr  step not written in source
changeField           4  -
rebind                8  new PassingSample$Item, PassingSample$Item.<init>
incrementNumber       5  -
extendText            4  dynamic:makeConcatWithConstants
changeField      [aload_0, bipush, putfield, return]
rebind           [new, dup, invokespecial, astore_0, aload_0, bipush, putfield, return]
```

The first row is this lesson's share of the course's thesis. Five arguments of five separate
types pass, and there are **five loading instructions** in the class file: `iload`, `lload`,
and three `aload`. All do the same work — putting the value in a local variable slot onto the
stack — and the only difference between them is the value's width. An integer takes one slot,
a long integer takes two; this is why the third argument loads with `aload_3` — the second
argument has already taken up two numbers.

What is not here speaks louder than what is. There is no instruction that copies an object.
There is no separate instruction that passes a reference by its address. The class file does
not have two separate paths named "pass by value" and "pass by reference"; there is **one**
path, and that path is copying the value sitting in the slot and putting it on the stack. For
a record, an array, and a string, the value sitting in the slot is a **reference**; what gets
copied is not the object, it is that reference. Java's passing model is this single sentence,
and the rest of the measurement is this sentence's consequences.

The table below separates two outcomes. `changeField` is **4** instructions and no step is
added: load the reference, load the value, write to the field. The write goes **into the
object** with `putfield`. `rebind`, by contrast, is **8** instructions and adds two steps: an
object production and a constructor call. The instruction sequence writes the continuation —
`astore_0`, meaning the new reference is written **over the slot.** The slot is the called
method's own slot; there is no link at all between it and the caller's slot.

The last two rows are the same distinction's counterpart on a primitive type and a string:
`incrementNumber` writes to the slot, `extendText` builds a new string with a dynamic call
and again writes to the slot.

#### The Visible Change and the Invisible

- **BS38** — No identity number is printed in the visibility measurement; identity is only
  asked with `==`.
- **BS39** — Before every measurement, the field is set to a known value, so what changed can
  be separated from what did not.
- **BS40** — In the immutable-type measurement, the primitive type and the string are tried
  in the **same** form. For both, only rebinding can be written, because a string has no
  writable member.

```java
// Visible.java — which change is visible to the caller
class Item {
    String name;
    int count;
    long weight;
}

public class Visible {
    static boolean sameObject(Item incoming, Item outside) { return incoming == outside; }
    static void changeField(Item k) { k.count = 99; }
    static void rebind(Item k) { k = new Item(); k.count = 99; }
    static void swap(Item a, Item b) { Item g = a; a = b; b = g; }
    static void writeArray(int[] d) { d[0] = 99; }
    static void incrementNumber(int count) { count = count + 1; }
    static void extendText(String s) { s = s + " x"; }

    public static void main(String[] args) {
        Item k = new Item();
        k.name = "north";
        k.count = 12;
        System.out.println("is the parameter the same object as outside: " + sameObject(k, k));

        changeField(k);
        System.out.println("count after changeField : " + k.count);
        k.count = 12;
        rebind(k);
        System.out.println("count after rebind      : " + k.count);

        Item a = new Item();
        a.name = "north";
        Item b = new Item();
        b.name = "south";
        swap(a, b);
        System.out.println("a.name, b.name after swap : " + a.name + " " + b.name);

        int[] d = {12, 18};
        writeArray(d);
        System.out.println("d[0] after writeArray     : " + d[0]);

        int count = 12;
        incrementNumber(count);
        String text = "north";
        extendText(text);
        System.out.println("number and text after     : " + count + " " + text);
    }
}
```

```
is the parameter the same object as outside: true
count after changeField : 99
count after rebind      : 12
a.name, b.name after swap : north south
d[0] after writeArray     : 99
number and text after     : 12 north
```

The first line confirms the previous section's sentence at run time: the reference in the
parameter and the reference outside point to **the same object.** The object was not copied.
This is why the second line is not surprising — writing to the field is visible to the
caller, `count` became **99.**

The third line places the distinction. `rebind` also writes **99** to a field, but because it
first binds the parameter to a new object, what it writes to is a separate object; the
caller's record stays **12.** What makes the difference is not the write itself, it is
**which object** is written to. The fourth line is the best-known form of this: a method
swapping two parameters does nothing to the caller, because what it swaps is two slots.

The fifth line is the same rule applied to an array: an array is an object too, the reference
in the parameter points to the same array, and the write `d[0] = 99` is visible to the caller.

An applicable rule follows from here. A method's caller-visible effect arises only when it
**writes into a reachable object**; an assignment to the parameter itself is never visible.
If we do not want the object the caller gave to change, either the object must have no
mutable member, or the method must build its own copy.

#### On an Immutable Type, the Distinction Cannot Be Observed

The last line is the lesson's bounding measurement, and it places two values side by side:
the number **12**, the text **north.** Neither changed.

That the number did not change is expected; `count` is a primitive type, and the value
sitting in the slot is the number itself. The real result is in the text. `extendText` took a
reference, the object that reference pointed to was the same as the caller's, and the caller
again saw no change at all. The previous lesson measured the answer: a string has no writable
member. The line `s = s + " x"` does not write into the object, it cannot; it builds a new
string and puts it in the parameter's slot.

What this means for the measurement is: **this lesson's question cannot be asked of an
immutable type.** A string parameter cannot be told apart from a primitive-type parameter, so
looking at a string cannot decide which model Java uses. What makes the distinction
observable is not the passing form, it is **whether the object has a mutable member.**

#### Summary

- There is a single form of passing an argument in the class file: putting the value sitting
  in the slot onto the stack. Five arguments of five separate types pass with five loading
  instructions; the only difference is how many slots the value occupies.
- On reference types, the value sitting in the slot is a reference; what gets copied is not
  the object, it is that reference, and the parameter and the outside name point to the same
  object.
- Writing to a parameter's field goes into the object with `putfield` and is **visible** to
  the caller; rebinding the parameter writes to the method's own slot with `astore` and is
  **not visible.**
- A method swapping two parameters does nothing to the caller; a method writing to an
  element of an array, by contrast, has a visible effect, because an array is an object too.
- The distinction cannot be observed on an immutable type: a string parameter cannot be told
  apart from a primitive-type parameter, because there is no member to write into the object.

#### Course Wrap-Up

The course opened with a single question: how many steps, and which steps, does a written
line turn into in the class file? Ten lessons asked this question in ten separate forms and
got the answer, every time, from a run measurement. Gathered together, a single rule comes
out: **in Java, syntax is a shorthand.** The source does not show the whole of the steps
placed into the class file; what it shows is in what form those steps will be requested.

| Lesson | Measured form | Added step | Bounding measurement |
|---|---|---|---|
| Source, Bytecode, and the Virtual Machine | comparing the source text with the class file | 8 of 12 methods, 13 added steps total | a concatenation sitting in the source is entirely absent from the class file |
| Program Life Cycle | entry point and initialization order | a member is added: default constructor, 3 fields and 5 methods | once a constructor is written, the added constructor disappears |
| Packages and Imports | import versus fully qualified name | zero steps: two files byte-for-byte identical | a package name grows the file by six bytes |
| Primitive Types and Wrappers | the crossing between a primitive type and its wrapper | `Integer.valueOf` and `Integer.intValue` | `==` is correct in the cache range, wrong outside it |
| Variables and Scope | where a declaration sits | a field instruction, or just a slot | block scope produces the same 20 instructions, with one fewer slot |
| Operators and Expressions | numeric promotion and compound assignment | `i2l` and `i2b` conversions, a branch on short circuit | no step is added on two constants |
| Control Flow | selection and loop forms | two separate lookup instructions for selection; 5 steps for the enhanced `for` over a list | the same loop produces 24 instructions and zero steps over an array |
| Arrays | array production, length, and access | array production; `arraylength`; a per-element write | covariance's type check does not show up in instruction count |
| Strings | concatenation, the pool, and the builder | a dynamic call; production and three calls in the builder | concatenating two constants is 2 instructions, zero steps |
| Value and Reference Passing | argument loading and writing to a parameter | production and a slot write on rebind | on an immutable type, the distinction cannot be observed |

The table's last column is the course's method. Every lesson also measured a situation where
the added step was **absent** or **disappeared**, because something found everywhere explains
nothing. Constant folding, a written constructor, the cache range, a primitive-typed array —
each draws the rule's boundary. The course's second reading comes from here too: instruction
count and added-step count do not move in the same direction, and fewer instructions does not
mean less work.

In this course, the class was used only as a container. Yet through the measurements the
class itself produced questions too: a constructor not written in the source, three fields
and five methods not written in the source, a change visible to the caller when a record's
field is written. The next course, Object-Oriented Java, takes the class out of being a
container and makes it the subject. The same measure will do its job there too — who decides
which method a call goes to, and when, can be read from the class file just the same.

