3.1 Class Declarations, Constructors, and Object Lifecycle
Key Takeaways
- Class declarations define blueprints using specific modifier ordering, where top-level classes are restricted to public or package-private access.
- The Java compiler inserts a default no-argument constructor calling super() only when no constructors are explicitly declared in the class body.
- Constructor chaining via this() or super() must strictly be the first statement in a constructor, preventing circular invocations and ensuring deterministic hierarchy construction.
- Initialization follows a rigid multi-phase lifecycle: superclass static members, subclass static members, superclass instance initializers/constructors, and finally subclass instance initializers/constructors.
Class Declarations, Constructors, and Object Lifecycle
In Java SE 21, classes represent the fundamental structural blueprints of object-oriented applications. Mastering class declarations, constructor mechanics, initialization sequences, and object lifecycle transitions is essential for writing defect-free enterprise software and scoring highly on the 1Z0-830 examination.
Anatomy of a Class Declaration
The Java Language Specification (JLS §8.1) defines the formal grammar for top-level and nested class declarations. A class declaration consists of optional annotations, access modifiers, non-access modifiers, the class keyword, the identifier name, optional generic type parameters, an optional extends clause, an optional implements clause, and the class body enclosed in braces:
// Formal class declaration structure
[Access Modifier] [Non-Access Modifiers] class ClassName<TypeParameters>
extends SuperClassName
implements InterfaceOne, InterfaceTwo {
// Member declarations: fields, methods, constructors, initializers, nested types
}
Top-Level Class Constraints and Compilation Units
- Top-Level Access Restrictions: A top-level class (declared directly inside a
.javacompilation unit) can only use eitherpublicaccess or package-private (default) access (omitting any access modifier keyword). Applyingprivateorprotectedto a top-level class results in a compile-time error. - Compilation Unit File Naming: A single
.javasource file can contain at most onepublictop-level class. If a public class is present, the filename must exactly match the public class name followed by the.javaextension, respecting case sensitivity (e.g.,public class PaymentServicemust reside inPaymentService.java). A compilation unit may contain multiple package-private top-level classes. - Non-Access Modifiers on Classes:
final: Declares that the class cannot be extended or subclassed by any other class (e.g.,java.lang.String,java.lang.Integer).abstract: Declares that the class cannot be directly instantiated with thenewoperator. It may contain abstract methods requiring subclass implementation.sealed/non-sealed: Controls and restricts which specific classes may extend this class via thepermitsclause.- Illegal Modifier Combinations: A class cannot be simultaneously declared
finalandabstract, as their semantics are diametrically opposed (finalmandates no subclasses, whileabstractmandates subclassing for instantiation). strictfp: Obsolete since Java 17 (all floating-point calculations are strictly IEEE 754 evaluated), but syntactically allowed for backward compatibility.
// Valid compilation unit: PaymentGateway.java
package com.payment.core;
import java.io.Serializable;
// Public top-level class matching file name
public final class PaymentGateway<T extends Serializable> implements AutoCloseable {
@Override
public void close() {
System.out.println("Gateway closed");
}
}
// Valid package-private top-level helper class in the same file
class GatewayHelper {
static void logTrace(String msg) {
System.out.println("[TRACE] " + msg);
}
}
Constructors and Compiler Synthesis Rules
A constructor is an executable code block invoked during object allocation to initialize an instance's fields and enforce initial invariant conditions.
Constructor Characteristics
- Name: Must exactly match the identifier of the declaring class (case-sensitive).
- Return Type: Must declare no return type whatsoever (not even
void). If a return type is declared, the compiler treats the construct as a regular instance method rather than a constructor. - Modifiers: May declare any access modifier (
public,protected, package-private,private). Constructors cannot be declaredstatic,final,abstract, orsynchronized.
public class AccountService {
private final String accountId;
// Legitimate parameterized constructor
public AccountService(String accountId) {
this.accountId = accountId;
}
// EXAM TRAP: Method declaring return type void, NOT a constructor!
public void AccountService(String accountId) {
System.out.println("This is a regular method, not a constructor!");
}
}
The Compiler-Generated Default Constructor
If and only if a class declares zero explicit constructors, the Java compiler automatically synthesizes a default no-argument constructor:
- Parameter List: Zero parameters (
no-arg). - Access Modifier: Matches the visibility of the declaring class (
publicfor public classes, package-private for package-private classes). - Body: Contains a single statement:
super();which delegates to the no-argument constructor of the direct superclass. - Synthesis Suppression: The moment a developer declares any explicit constructor (regardless of its parameter count or access modifier, including
private), the compiler suppresses default constructor synthesis completely.
class Vehicle {
protected int wheels;
// Explicit parameterized constructor suppresses default constructor
public Vehicle(int wheels) {
this.wheels = wheels;
}
}
class Truck extends Vehicle {
// COMPILE ERROR if Truck has no explicit constructor!
// The compiler attempts to insert:
// public Truck() { super(); }
// but Vehicle has no no-arg Vehicle() constructor!
// Fix: Explicitly invoke the superclass parameterized constructor
public Truck() {
super(18);
}
}
Constructor Chaining: this() and super() Mechanics
Constructor chaining allows constructor overloads within the same class to reuse initialization logic or delegate upward to superclass initializers.
public class ServerConfig {
private final String host;
private final int port;
private final boolean ssl;
// Default configuration: delegates to 2-arg constructor
public ServerConfig() {
this("localhost", 8080);
}
// 2-arg configuration: delegates to master 3-arg constructor
public ServerConfig(String host, int port) {
this(host, port, true);
}
// Master 3-arg constructor
public ServerConfig(String host, int port, boolean ssl) {
super(); // Implicit or explicit call to Object constructor
this.host = host;
this.port = port;
this.ssl = ssl;
}
}
Strict Rules Governing Explicit Constructor Invocations
- First Statement Requirement: An explicit constructor invocation (
this(...)orsuper(...)) must strictly be the first executable statement in the constructor body. Placing any statement, method call, or variable declaration beforethis(...)orsuper(...)triggers a compile-time error in Java SE 21. - Mutual Exclusivity: A constructor cannot contain both
this(...)andsuper(...). Invokingthis(...)passes the responsibility of callingsuper(...)to the targeted sibling constructor. - No Circular Invocations: Circular constructor delegation chains (such as Constructor A invoking
this()to Constructor B, which invokesthis()back to Constructor A) are identified byjavacand rejected with arecursive constructor invocationcompilation error. - Pre-Construction State Access: Expressions passed as arguments to
this(...)orsuper(...)cannot access instance variables or call instance methods on the uninitialized instance (this), because the superclass object state has not yet completed its construction. Static fields and static helper methods can be safely passed as arguments.
Complete Multi-Phase Initialization Sequence
Java guarantees deterministic order of execution for static initializers, instance fields, and constructor bodies across complex inheritance hierarchies.
class Ancestor {
static String staticAncestor = trace("1. Ancestor static field");
String instanceAncestor = trace("5. Ancestor instance field");
static { trace("2. Ancestor static block"); }
{ trace("6. Ancestor instance block"); }
public Ancestor() {
trace("7. Ancestor constructor body");
}
static String trace(String msg) {
System.out.println(msg);
return msg;
}
}
class Descendant extends Ancestor {
static String staticDescendant = trace("3. Descendant static field");
String instanceDescendant = trace("8. Descendant instance field");
static { trace("4. Descendant static block"); }
{ trace("9. Descendant instance block"); }
public Descendant() {
super();
trace("10. Descendant constructor body");
}
}
Deterministic Initialization Phases
| Phase | Category | Executed Elements | Timing and Cardinality |
|---|---|---|---|
| Phase 1 | Class Loading | Superclass static variable initializers & static { ... } blocks | Evaluated in textual order; executes once when superclass is loaded. |
| Phase 2 | Class Loading | Subclass static variable initializers & static { ... } blocks | Evaluated in textual order; executes once when subclass is loaded. |
| Phase 3 | Instantiation | Superclass instance variable initializers & { ... } instance blocks | Evaluated in textual order; executes upon each new instantiation. |
| Phase 4 | Instantiation | Superclass constructor body | Executes immediately following superclass instance initializers. |
| Phase 5 | Instantiation | Subclass instance variable initializers & { ... } instance blocks | Evaluated in textual order; executes upon each new instantiation. |
| Phase 6 | Instantiation | Subclass constructor body | Executes to completion, returning the fully initialized instance reference. |
Object Lifecycle, Reachability, and Cleaners
An object's lifecycle begins upon heap allocation and concludes when its memory is reclaimed by the Garbage Collector (GC).
public class MemoryTracker {
public static void runSimulation() {
Node nodeA = new Node("A"); // Node A allocated (1 live reference)
Node nodeB = new Node("B"); // Node B allocated (1 live reference)
nodeA.next = nodeB; // A references B
nodeB.next = nodeA; // B references A (Circular Island)
nodeA = null; // Node A has no local root reference
nodeB = null; // Node B has no local root reference
// Both Node A and Node B form an isolated island: ELIGIBLE FOR GC
}
}
Object Reachability and GC Mechanics
- GC Roots: Live references residing in active thread call stacks (local variables, parameters), static class variables, and JNI handles form root sets.
- Unreachable Objects: Any object that cannot be reached through a continuous chain of references starting from a GC Root is eligible for immediate reclamation.
- Islands of Isolation: Circular reference graphs where isolated objects reference each other but are disconnected from all live GC Roots are immediately eligible for garbage collection.
System.gc(): Submits a non-binding hint requesting GC execution. The JVM is never obligated to honor or immediately execute this request.- Deprecation and Removal of
finalize(): Thefinalize()method was deprecated in Java 9 and permanently marked for removal. Modern Java SE 21 applications must implement deterministic resource cleanup viajava.lang.AutoCloseablewithtry-with-resources, or register asynchronous cleanup actions usingjava.lang.ref.CleanerandPhantomReference.
Common 1Z0-830 Exam Traps
- Method Looking Like Constructor: Watch for return types on constructor declarations (e.g.,
public void User()). It compiles as a method and will not be invoked bynew User(). - Missing Superclass Default Constructor: If a superclass defines only
SuperClass(int x)and a subclass constructor does not explicitly writesuper(val), compilation fails on the subclass constructor header. - Static Blocks Running Once: Static initialization runs only upon the initial loading and linking of the class; instantiating multiple objects does not re-execute static initializers.
- Illegal Initializer Placement: Attempting to put statements before
this()orsuper()inside constructor bodies triggers compiler errors.
Given the following class definitions:
Which statement correctly completes Line 1 so that the code compiles successfully?class Device {
protected String name;
public Device(String name) {
this.name = name;
}
}
class Sensor extends Device {
private double calibration;
public Sensor(String name, double calibration) {
// Line 1
this.calibration = calibration;
}
}
Examine the following Java program:
What is the exact console output when running Beta?class Alpha {
static { System.out.print("A "); }
{ System.out.print("B "); }
Alpha() { System.out.print("C "); }
}
class Beta extends Alpha {
static { System.out.print("D "); }
{ System.out.print("E "); }
Beta() {
super();
System.out.print("F ");
}
public static void main(String[] args) {
System.out.print("1 ");
new Beta();
System.out.print("2 ");
new Beta();
}
}
Consider the following constructor declarations inside a class named Transaction:
Why does this code fail to compile?public class Transaction {
private String id;
private double amount;
public Transaction(String id) {
this(id, 0.0);
}
public Transaction(String id, double amount) {
this.id = id;
this.amount = amount;
}
public Transaction() {
System.out.println("Creating default");
this("TX-000");
}
}
Consider the following reference allocations in a method:
At Point X and Point Y, how many Node objects are eligible for garbage collection?public void processNodes() {
Node n1 = new Node("Alpha"); // Node Alpha
Node n2 = new Node("Beta"); // Node Beta
Node n3 = new Node("Gamma"); // Node Gamma
n1.neighbor = n2;
n2.neighbor = n1;
n3.neighbor = n2;
n1 = null;
n2 = null;
// Point X
n3 = null;
// Point Y
}