---
title: 'Class, Object, and Constructor'
source: 'https://academia.sh/en/courses/java-object-model/class-object-and-constructor'
course: 'Object-Oriented Java'
language: en
updated: '2026-08-17T18:09:40+00:00'
license: 'CC BY-SA 4.0'
---

# Class, Object, and Constructor

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

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

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

## One Name, Two Types

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

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

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

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

## The Measurement Core

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

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

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

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

    String label = "base field";

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

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

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

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

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

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

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

## In What Order Does the Constructor Chain Run

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

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

```
declared type Base, runtime type Sub

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

fragile setup   : NullPointerException
delivered object: null
```

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

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

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

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

## The Cost of a Half-Built Object

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Where Does the Call Bind in the Class File

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

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

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

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

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

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

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

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

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

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

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

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

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

## Summary

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

## Next Step

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