SaveMyCert
Log in
5 of 5 free questions left today·for 30 a day
Applying Object-Oriented Principles in Java Programs

Java Classes, Records, Constructors and Initializers (1Z0-831)

18 min read1Z0-831 · Applying Object-Oriented Principles in Java ProgramsUpdated

A Java class declares instance and static fields, methods, constructors and initializer blocks, and a record is a special class whose final fields, canonical constructor, accessors, equals, hashCode and toString are generated from a single header — Oracle 1Z0-831 objective 3.2 asks you to write both and to predict exactly what happens when they are constructed. This lesson covers instance versus static members, the default constructor and constructor chaining, Java 25 flexible constructor bodies, the precise order of static and instance initialization, final-field rules, and how compact and delegating record constructors work.

What you’ll learn
  • Distinguish instance from static fields and methods, and predict shared static state across objects (including in records)
  • Explain when the default constructor exists, how this(...) and super(...) chain, and when the implicit super() fails to compile
  • Apply Java 25 flexible constructor body rules: what a prologue may and may not do before this(...) or super(...)
  • Trace the full order of static and instance initialization across a class hierarchy, including overridable calls from constructors
  • Decide when a class is initialized, including why reading a constant variable does not trigger initialization
  • Apply definite-assignment rules to final fields and checked-exception rules to instance initializers
  • Use records correctly: generated members, compact and non-canonical constructors, and what a record body may declare

Instance and static members: one copy per object versus one per class

An instance field or method belongs to each object, while a static field or method belongs to the class itself: every new creates a fresh set of instance fields, but a static field is a single variable shared by all instances (and reachable without any instance, as ClassName.field).

Instance memberStatic member
How many copiesOne per objectOne per class
Starting valueDefault (0, false, null) set for each new objectDefault, set once when the class is prepared
Accessed throughA reference: obj.f, or this.f / plain f inside instance codeClassName.f (a reference also works, but the reference's object is ignored)
Can use this?YesNo — there is no current object

Static code cannot name an instance member without saying which object: inside static void main, an unqualified count++ for an instance field fails with non-static variable count cannot be referenced from a static context. Instance code, by contrast, can freely read and write static fields. That is how per-class counters and ID generators work — a constructor increments a static field, so its value counts every object built, while an instance field incremented in the same constructor is 1 in every object:

class Order {
    static int nextId = 1;
    final int id;
    String status = "NEW";

    Order() {
        this("NEW");
    }

    Order(String status) {
        this.status = status;
        id = nextId++;
    }

    static Order rush() {
        return new Order("RUSH");
    }
}

public class Main {
    public static void main(String[] args) {
        Order a = new Order();
        Order b = Order.rush();
        System.out.println(a.id + " " + a.status + " "
                + b.id + " " + b.status + " " + Order.nextId);
    }
}

This prints 1 NEW 2 RUSH 3: nextId is shared, so each construction takes the next number; id and status belong to each object. A static factory method such as rush() is ordinary static code that happens to call a constructor.

Constructors: declaration rules and the default constructor

A constructor is a member with the class's exact name and no return type; it runs once per new to initialize the new object. It can have any access modifier, parameters and a throws clause, but it is not inherited and cannot be static, final or abstract.

Two rules decide most constructor questions:

  • The default constructor exists only if you declare none. When a class has no constructor at all, the compiler adds a no-argument one (with the class's access level) whose body is just super();. The moment a class declares a constructor of any shape, that free constructor is gone: a Crate that declares only Crate(String label) has no Crate() unless you write one, and every call site that relied on it must change.
  • A return type makes it a method. A member such as int Gauge() { ... } inside class Gauge is a legal ordinary method that merely shares the class name (the compiler may warn, but it compiles). new never calls a method, so its body runs only when something invokes it by name. If the class declares no real constructor, it still receives the default one.

Constructors can be overloaded like methods — several constructors with different parameter lists — and new picks one by the same overload-resolution rules (covered with overloading in objective 3.3).

Constructor chaining: this(...), super(...) and the implicit super()

Every constructor either delegates to another constructor of the same class with this(...), or calls a superclass constructor with super(...); if you write neither, the compiler inserts super(); for you. A constructor may contain at most one such explicit invocation, and it cannot contain both.

this(...) chains run inside-out. A delegation is an ordinary call: the target constructor runs to completion before the caller continues with the statements after its this(...). In a chain first → second → third, the body of third finishes first, then the rest of second, then the rest of first — so anything the callers do after delegating happens later than, and can overwrite, what the target did. Default arguments are the usual reason for such chains: Kettle() calls this(1.5), which supplies the capacity to the one constructor that actually stores state. Eventually exactly one constructor in the chain calls super(...) (explicitly or implicitly), so the superclass is initialized once per object, never once per delegation.

The implicit super() needs a matching parent constructor. The inserted call is always the no-argument super(), so it compiles only if the direct superclass has an accessible no-argument constructor — declared, or the default one. When Engine declares only Engine(String fuel), every Turbo constructor that does not invoke something explicitly fails with constructor Engine in class Engine cannot be applied to given types. A class with no constructors at all is no escape: its default constructor contains the same super(). The problem is solved on one side or the other — the subclass names an existing superclass constructor with matching arguments, or the superclass gains a no-argument constructor.

Flexible constructor bodies (Java 25): the prologue and the epilogue

Since Java 25 (JEP 513, now a standard feature), this(...) or super(...) no longer has to be the first statement: statements before it form the prologue, statements after it the epilogue. The prologue exists to validate arguments, compute values for the call and initialize the class's own fields before the superclass constructor runs.

Until the explicit constructor invocation completes, the object is not usable, so the prologue is an early construction context with strict limits:

In the prologue you mayIn the prologue you may not
Use parameters and declare/use local variablesRead any instance field of the object, qualified (this.width) or by simple name
Call static methods and read or write static fields (a counter, a cache)Call instance methods, qualified or not, or pass this anywhere
Create other objects — except an inner-class instance of the object being built, which would need thisUse return — 'return' not allowed before explicit constructor invocation
Throw exceptions (validation that fails fast)Assign a field that has an initializer at its declaration, or any inherited field
Assign the class's own instance fields that have no initializer (this.width = w;)

The epilogue is an ordinary constructor body: it may use this, call methods and return early. Prologues work the same way for this(...) — a constructor can strip or parse its argument, then delegate with the cleaned value. The arguments of this(...) and super(...) are themselves part of the early construction context: they follow the same table, so they can read a static field or call a static method, but not an instance method.

Assigning fields early is not just a convenience. A superclass constructor that calls an overridable method sees subclass fields at their default values (next section) — unless the subclass assigned them in its prologue:

class Base {
    Base() { describe(); }
    void describe() { System.out.println("base"); }
}

class Sensor extends Base {
    private final String unit;

    Sensor(String unit) {
        if (unit == null || unit.isBlank()) {
            throw new IllegalArgumentException("unit");
        }
        this.unit = unit.strip();
        super();
        System.out.println("ready");
    }

    @Override
    void describe() { System.out.println("unit=" + unit); }
}

public class Main {
    public static void main(String[] args) {
        new Sensor(" kPa ");
    }
}

This prints unit=kPa and then ready. Move this.unit = ... after super() and the first line becomes unit=null, because the overriding describe() would run before the field is set.

Order of object construction and instance initializers

When new creates an object, Java first sets every instance field to its default value, then runs the constructor chain so that the superclass part is fully built before the subclass part. The work happens in two sweeps:

  1. Upward, prologues. The invoked constructor runs its prologue (if any), then reaches its explicit or implicit super(...), which enters the superclass constructor; that constructor runs its prologue and reaches its own super(...), and so on up to Object. Prologues therefore run from the subclass up.
  2. Downward, everything else. As each super(...) returns, starting from the class just below Object, that class runs its instance field initializers and instance initializer blocks ({ ... }) together, in source order, and then the rest of its constructor body (the epilogue). Initializers and bodies therefore run from the top class down.

With three classes Top ← Mid ← Leaf, each printing in its prologue, initializer and body, new Leaf() prints leaf-pro mid-pro top-pro top-body mid-init mid-body leaf-init leaf-body.

Field initializers and instance blocks form one queue in textual order, so a block between two fields runs between their initializers. A constructor that delegates with this(...) does not run the initializers itself; they run once, in the constructor that calls super(...).

class Animal {
    static { System.out.print("1 "); }
    { System.out.print("3 "); }
    Animal() { System.out.print("4 "); }
}

class Dog extends Animal {
    static { System.out.print("2 "); }
    String name = init();
    Dog() { System.out.print("6 "); }
    String init() { System.out.print("5 "); return "rex"; }
}

public class Main {
    public static void main(String[] args) {
        new Dog();
    }
}

This prints 1 2 3 4 5 6: both static blocks first (parent then child, once per class — next section), then Animal's instance block and constructor, then Dog's field initializer (which calls init()) and finally Dog's constructor body. Creating a second Dog repeats only 3 4 5 6.

Overridable calls during construction. Method dispatch uses the object's runtime class even inside a superclass constructor. When a superclass constructor calls a method that the subclass overrides, the override runs before the subclass's own field initializers and constructor body, so any subclass field it reads still holds its default (null, 0, false) — even a field declared with an initializer. The same method called later, from the subclass's constructor body or afterwards, sees the initialized value. Calling overridable methods from constructors is therefore fragile; Java 25's prologue assignment is the clean fix when the subclass controls the field.

Checked exceptions in instance initializers. The compiler treats an instance initializer as code copied into every constructor that calls super(...). An initializer block has no syntax for a throws clause, so the rule (JLS §8.6) is: a checked exception the block can throw must be handled inside the block, or appear in the throws clause of each of the class's constructors — one constructor that does not declare it is enough for unreported exception errors. Static initializers are stricter still: they may not throw checked exceptions at all.

Static initializers and when a class is initialized

A static initializer (static { ... }) and the static field initializers run once per class, in textual order, when the class is initialized — and a class is initialized lazily, on its first active use: creating an instance, calling a static method, or reading or writing a static field that is not a constant. Before a class is initialized, its superclass is initialized (so parent static blocks print before child static blocks), and a program's main class is initialized before main runs.

Because static fields begin at their defaults and are initialized top to bottom, a method called from an earlier initializer can observe a later field before its initializer has run — and sees its default value. A direct forward reference by simple name (static int a = b;) is rejected by the compiler (illegal forward reference), but a method call hides it from the compiler. The same applies to instance fields, which are also initialized in textual order.

class Port {
    static final int DEFAULT = 8080;
    static final int FALLBACK = Integer.parseInt("9090");
    static { System.out.print("Port "); }
}

class Clock {
    static long started = stamp();
    static long offset = 40;
    static long stamp() { return offset + 2; }
}

public class Main {
    public static void main(String[] args) {
        System.out.print(Port.DEFAULT + " ");
        System.out.print(Port.FALLBACK + " ");
        System.out.println(Clock.started + " " + Clock.offset);
    }
}

This prints 8080 Port 9090 2 40. Port.DEFAULT prints without initializing Port: it is a constant variable — a final field of primitive or String type initialized with a compile-time constant expression — so the compiler inlines its value into the caller. FALLBACK is also final int, but a method call is not a constant expression, so reading it is an active use: the static block runs and Port appears just before 9090. In Clock, stamp() runs while offset is still 0, so started is 2, and offset receives 40 only afterwards.

Static field read from another classInitializes the class?
static final int SIZE = 4 * 16; / static final String TAG = "v" + 2;No — constant expression, inlined
static final Long LIMIT = 9L;Yes — a wrapper type is never a constant variable, even with a literal
static final int SEED = compute();Yes — the initializer is not a constant expression
static String region; (non-final)Yes, the first time; later reads do not re-run the static block

Final fields and definite assignment

A final instance field must be assigned exactly once before the constructor finishes, on every path through every constructor. A blank final (declared without an initializer) is the flexible case; a final with an initializer can never be assigned again.

Where a blank final serial is assignedEffect on the rule
An instance initializer blockCounts for every constructor; no constructor may assign it again
Inside constructorsEach constructor that does not delegate must assign it on every path through its body (including each if/else branch)
A constructor that starts with this(...)Covered by the target constructor; assigning it again after the call is a double assignment
Any method (instance or static)Never allowed — cannot assign a value to final variable serial, even if only constructors call the method

A constructor path that misses the assignment fails with variable serial might not have been initialized; a path that assigns twice fails with variable serial might already have been assigned. Reading a blank final before it is assigned — by simple name in a constructor — is also a compile error, though an instance method called early can still see its default value (the same trap as the forward read above).

Static finals follow the same rule at class level: assigned exactly once, in the declaration or in a static initializer. Records rely on this — every record component field is private and final, so it is assigned exactly once by the canonical constructor.

Records: what the header generates

A record is a class whose state is declared once, in its header — record Seat(int row, char letter) {} — and the compiler generates the rest: a private final field per component, a canonical constructor taking the components in order (with the record's own access level: public for a public record, package access for one with no modifier), a public accessor per component named exactly after it (s.row(), not getRow()), and equals, hashCode and toString based on the components.

  • toString lists the record's name and its components as name=value pairs in declaration order. The API leaves the exact format unspecified, but current JDKs use square brackets and comma-separated pairs: Seat[row=4, letter=C].
  • equals is value-based: two records of the same type with equal components are equal, whichever constructor built them.
  • A record is implicitly final and extends java.lang.Record, so it cannot extend another class (it can implement interfaces — inheritance details belong to objectives 3.5 and 3.6).
  • An accessor returns exactly the declared component type, so ordinary Java typing rules apply to whatever you do with it — numeric components combine numerically, and nothing is converted to text unless a String is involved.

You may still write any of the generated members yourself (an explicit accessor must be public, take no parameters and return the component type), and the body may add other methods.

Record constructors: canonical, compact and delegating

A record can customise construction in three ways, and every path ends in the canonical constructor:

FormLooks likeRules
Explicit canonicalPair(int left, int right) { ... }Same parameter names and types as the header; must assign every field (this.left = left;)
Compact canonicalRange { ... } (no parameter list)Works on implicit parameters; may validate or reassign the parameters; the fields are assigned from them automatically at the end, so assigning a field yourself (this.start = ...) is a compile error
Non-canonicalMoney(long cents) { ... }Must delegate with this(...), eventually reaching the canonical constructor; may not assign fields directly (constructor is not canonical, so it must invoke another constructor)

A compact constructor is therefore the place for validation (throw for bad input) and normalisation (trim a name, clamp a percentage to 0–100, take a defensive copy of a list with List.copyOf). Because non-canonical constructors must delegate to it, its normalisation applies on every construction path. Non-canonical constructors can use a Java 25 prologue to compute the arguments before this(...):

record Email(String user, String domain) {
    static int created;

    Email {
        if (user.isBlank()) {
            throw new IllegalArgumentException("user");
        }
        domain = domain.toLowerCase();
        created++;
    }

    Email(String address) {
        String[] parts = address.split("@");
        this(parts[0], parts[1]);
    }

    String address() { return user + "@" + domain; }
}

public class Main {
    public static void main(String[] args) {
        Email a = new Email("ann", "Example.COM");
        Email b = new Email("ann@example.com");
        System.out.println(a);
        System.out.println(a.equals(b) + " " + b.address());
        System.out.println(Email.created);
    }
}

This prints Email[user=ann, domain=example.com], then true ann@example.com, then 2: both objects pass through the compact constructor, which lower-cases the domain and increments the static counter.

What a record body may declare follows from one idea: a record's instance state is exactly its components. So the body cannot add instance state — an extra instance field fails with field declaration must be static, and an instance initializer block with instance initializers not allowed in records. Everything that does not add instance state is permitted: static fields, static initializers and static methods (shared counters and factories, as created shows above), instance methods that work from the components, nested types, and constructors that obey the table above.

Tip. Objective 3.2 is examined through short, complete programs that you trace by hand. Expect "What is the result?" items that print markers from static blocks, instance blocks, field initializers and constructors across a parent and child class, or across chained this(...) calls — sometimes with two objects created, so you must know which parts run once. Other items show a record and ask for its toString, accessor results or the effect of a compact constructor, or follow a static field shared by several objects. "Which change makes the code compile? (Select TWO.)" items target the default constructor and implicit super(), blank final fields, record bodies, Java 25 constructor prologues and checked exceptions in instance initializers. "Compilation fails" is a real option on many items, and some programs depend on initialization timing rather than on what the code appears to say.

Key takeaways
  • A static field is one variable per class shared by every instance; an instance field is a fresh copy in each object, and static code cannot use one without naming an object.
  • The default no-argument constructor exists only when a class declares no constructors; a member with a return type, even void, is a method, not a constructor.
  • A constructor without an explicit this(...) or super(...) gets an implicit super(), which needs an accessible no-argument superclass constructor.
  • this(...) chains complete inside-out; only the constructor that calls super(...) runs the instance initializers, once per object.
  • Java 25 prologues may use parameters, locals, static methods and static fields, throw, and assign the class's own fields without initializers — but never read instance fields, call instance methods, use this or return.
  • Construction order: prologues run from the subclass up; then, from the top class down, each class runs its field initializers and instance blocks in source order followed by its constructor body. Static parts run once per class, parent first, on first active use.
  • A superclass constructor calling an overridden method sees subclass fields at their defaults unless the subclass assigned them in its prologue.
  • Reading a constant variable (final primitive or String with a constant initializer) is inlined and does not initialize the class; a final Integer is not a constant.
  • A blank final field must be assigned exactly once on every constructor path; a checked exception from an instance initializer must be caught there or declared by every constructor.
  • Records generate private final fields, a canonical constructor with the record's access level, public name-matched accessors and a component-listing toString (brackets in current JDKs); compact constructors reassign parameters, non-canonical ones must delegate, and records allow no extra instance fields or instance initializers.

Frequently asked questions

When does Java add a default constructor to a class?

Java adds a default no-argument constructor only when a class declares no constructors at all. It has the class's access level and simply calls super(). As soon as you declare any constructor, the default one is no longer generated, so code calling new ClassName() stops compiling unless you also declare a no-argument constructor yourself.

What are flexible constructor bodies in Java 25?

Flexible constructor bodies (JEP 513, standard in Java 25) let a constructor run statements before its explicit this(...) or super(...) call. That prologue may use parameters and local variables, call static methods, read and write static fields, throw exceptions and assign the class's own instance fields that have no initializer. It may not read instance fields, call instance methods, use this or return; those are allowed only after the call.

In what order do initializers and constructors run in Java?

Static initialization runs once per class on first active use, superclass before subclass, with static field initializers and static blocks in source order. For each new object, constructor prologues run first, from the subclass up to Object; then, from the top class down, each class runs its instance field initializers and instance initializer blocks in source order, followed by the rest of its constructor body.

Does reading a static final field initialize the class?

Not if the field is a constant variable: a final field of primitive or String type initialized with a compile-time constant expression. Its value is inlined by the compiler, so the class's static initializers do not run. Any other static field read, including a final Integer or a final field set by a method call, is an active use that initializes the class.

What is a compact canonical constructor in a Java record?

A compact canonical constructor is a record constructor written without a parameter list, such as Percent { ... } inside record Percent(int value). Its body sees the components as implicit parameters, which it can validate or reassign; the compiler then assigns the record's fields from those parameters at the end. Assigning a field such as this.value inside a compact constructor is a compile-time error.

Can a record have extra fields or additional constructors?

A record can declare static fields, static initializers, static and instance methods and nested types, but not extra instance fields and not instance initializer blocks. It can declare additional (non-canonical) constructors, but each one must delegate with this(...) so that every construction path ends in the canonical constructor.

Why does calling an overridable method from a constructor print null?

Method calls are dispatched on the object's runtime class, even while a superclass constructor is running. The subclass's override therefore runs before the subclass's field initializers and constructor body, so it sees its fields at default values such as null or 0. In Java 25 a subclass can avoid this by assigning the field in its constructor prologue, before super(...).

How must a final instance field be initialized in Java?

A final instance field must be definitely assigned exactly once before every constructor completes. Assign it at its declaration, in an instance initializer block, or in each constructor (a constructor may instead delegate with this(...) to one that assigns it). It can never be assigned inside an ordinary method, and assigning it twice on any path is a compile-time error.

Source

This lesson covers the "Applying Object-Oriented Principles in Java Programs" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?