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;
privatemakes them inaccessible from outside the class — a core encapsulation choicefinalmeans 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:
- Disambiguating parameter names from field names:
this.balance = balance;— withoutthis,balance = balancewould assign the parameter to itself - Passing the current object:
someMethod(this)— handing a reference to yourself to another method - 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
voidif 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.0accountNumberis non-null and formatted correctlyholderNameis 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.