Skip to content
Part IA Michaelmas Term

Java Class Structure

Anatomy of a Class

Every Java class is composed of several categories of members. A well-structured class typically contains, in order: fields, constructors, and methods. Here is a complete BankAccount example with full annotation:

public class BankAccount {
    private final String accountNumber;
    private double balance;
    private final String holderName;
    private static int nextId = 1;

    public BankAccount(String holderName) {
        this.accountNumber = "ACC-" + String.format("%04d", nextId);
        nextId = nextId + 1;
        this.holderName = holderName;
        this.balance = 0.0;
    }

    public BankAccount(String holderName, double initialDeposit) {
        this(holderName);
        this.balance = initialDeposit;
    }

    public void deposit(double amount) {
        if (amount <= 0.0) {
            throw new IllegalArgumentException("Deposit must be positive");
        }
        this.balance = this.balance + amount;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0.0) {
            return false;
        }
        if (amount > this.balance) {
            return false;
        }
        this.balance = this.balance - amount;
        return true;
    }

    public double getBalance() {
        return this.balance;
    }

    public String getAccountNumber() {
        return this.accountNumber;
    }

    public String getHolderName() {
        return this.holderName;
    }

    public String toString() {
        return "BankAccount[" + this.accountNumber
            + ", holder=" + this.holderName
            + ", balance=" + this.balance + "]";
    }

    public static int getNextId() {
        return nextId;
    }
}

Fields (Instance Variables)

Fields hold the state of an object. Each instance gets its own copy:

private final String accountNumber;
private double balance;
private final String holderName;
  • private makes them inaccessible from outside the class — a core encapsulation choice
  • final means the reference (or primitive value) cannot be reassigned after the constructor completes
  • Field names follow camelCase convention

Fields that do not change after construction are marked final to enforce immutability of that aspect of the object’s state.

Constructors

Constructors are special methods that initialise a new object. They share the class name, have no return type (not even void), and are invoked with new:

public BankAccount(String holderName) {
    this.accountNumber = "ACC-" + String.format("%04d", nextId);
    nextId = nextId + 1;
    this.holderName = holderName;
    this.balance = 0.0;
}

The constructor’s job is to establish the class invariant — the set of conditions that must hold for every valid object. For a BankAccount, invariants might include: balance >= 0, accountNumber is non-null and unique, holderName is non-null.

Constructor body pattern: initialise final fields, assign remaining fields, increment any shared static counters. A constructor should leave the object in a consistent, usable state.

The this Keyword

this refers to the current instance — the object on which the method was called. Three uses:

  1. Disambiguating parameter names from field names: this.balance = balance; — without this, balance = balance would assign the parameter to itself
  2. Passing the current object: someMethod(this) — handing a reference to yourself to another method
  3. Constructor chaining: this(holderName) — calling another constructor of the same class

Not every method needs this — you can write balance instead of this.balance when there is no local variable with the same name. However, using this consistently for field access makes the code’s intent clearer.

Methods (Instance Methods)

Instance methods define the behaviour of objects. They are called on an object reference using dot notation:

BankAccount account = new BankAccount("Ada");
account.deposit(100.0);
double bal = account.getBalance();

A method has:

  • An access modifier (public, private, etc.)
  • A return type (or void if nothing is returned)
  • A name (camelCase, typically a verb or verb phrase)
  • A parameter list (enclosed in parentheses)
  • A body (enclosed in braces)

Inside an instance method, this refers to the object the method was called on.

Accessors (Getters) and Mutators (Setters)

public double getBalance() {     // accessor — reads state
    return this.balance;
}

public void setBalance(double b) { // mutator — writes state
    this.balance = b;
}

Accessors retrieve field values without exposing the field directly. Mutators modify state with the opportunity to validate. Not every field needs a setter — accountNumber is final and has only a getter.

toString()

toString() returns a human-readable representation of the object. It is called implicitly when an object is concatenated with a string or passed to System.out.println():

System.out.println(account);

If you do not override toString(), the default implementation (inherited from Object) returns something like BankAccount@1a2b3c4d — the class name followed by the hash code in hex. Always override toString() for debuggability.

equals()

equals(Object other) compares two objects for logical equality. The default implementation from Object uses reference equality (==). Override equals() when you want objects to be compared by their field values rather than by their identity. A proper equals() override must satisfy the contract: reflexive, symmetric, transitive, consistent, and equals(null) returns false. The companion method hashCode() must also be overridden.

The Class Invariant

The class invariant is a logical condition that must be true for every valid instance, at all times that the object is accessible from outside the class. In BankAccount:

  • balance >= 0.0
  • accountNumber is non-null and formatted correctly
  • holderName is non-null

Constructors establish the invariant. Every public method preserves it. Private helper methods may temporarily break it, but the public methods that call them must restore it before returning. A well-designed class never lets an external observer see a broken invariant.