Understand how classes define structure and objects hold state.
Published September 21, 2026
Java is an object-oriented language: programs are built from objects that bundle data (what they know) with behaviour (what they can do). A class is the definition, the blueprint that says which data an object holds and which operations it supports. An object (or instance) is one concrete thing built from that blueprint, with its own values.
One BankAccount class can produce millions of account objects. Each has its own owner and balance, but they all follow the same rules for deposits and withdrawals.
public class BankAccount {
// Fields โ the state each object carries
private final String accountNumber;
private final String owner;
private long balanceInPaise; // money as whole paise/cents: no floating-point rounding errors
// Constructor โ runs once, when the object is created; establishes a valid starting state
public BankAccount(String accountNumber, String owner) {
this.accountNumber = Objects.requireNonNull(accountNumber);
this.owner = Objects.requireNonNull(owner);
this.balanceInPaise = 0;
}
// Methods โ behaviour; they enforce the rules for changing state
public void deposit(long amount) {
if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive");
balanceInPaise += amount;
}
public void withdraw(long amount) {
if (amount <= 0) throw new IllegalArgumentException("Withdrawal must be positive");
if (amount > balanceInPaise) throw new IllegalStateException("Insufficient funds");
balanceInPaise -= amount;
}
public long getBalance() { return balanceInPaise; }
public String getOwner() { return owner; }
}
The parts:
private means only code inside this class can touch them directly. final means the field can't be reassigned after the constructor finishes.this refers to the current object, and this.owner = owner disambiguates the field from a parameter with the same name.BankAccount a = new BankAccount("ACC-1", "Asha");
BankAccount b = new BankAccount("ACC-2", "Ben");
a.deposit(50_000);
b.deposit(20_000);
a.withdraw(10_000);
System.out.println(a.getBalance()); // 40000 โ a and b have completely separate state
System.out.println(b.getBalance()); // 20000
new allocates the object on the heap, runs the constructor, and returns a reference, a handle pointing to the object. The variable a holds that reference, not the object itself.
Because variables hold references, assignment copies the reference, not the object:
BankAccount x = new BankAccount("ACC-9", "Chen");
BankAccount y = x; // y points to the SAME object
y.deposit(1_000);
System.out.println(x.getBalance()); // 1000 โ changed "through" y
== on objects compares references (is it the same object?), not content. To compare content, a class overrides equals (see equals() and hashCode()).null (pointing to no object). Calling a method on it throws NullPointerException.free.Compare these two designs:
// โ Public field โ any code anywhere can break the rules
public class Account { public long balance; }
account.balance = -5_000_000; // nothing stops this
// โ
Private field + methods that enforce the rules
account.withdraw(5_000_000); // throws "Insufficient funds"
Encapsulation means an object protects its own invariants, the rules that must always hold (such as "balance is never negative"). Callers ask the object to do things through methods, and the object decides how. Benefits:
long to BigDecimal doesn't break callers.A common anti-pattern is generating a getter and a setter for every field. That's just a public field with extra typing. Prefer methods named after behaviour (deposit, withdraw, close), and add setters only where arbitrary change is genuinely valid.
public class BankAccount {
private static long accountsCreated = 0; // ONE copy shared by the whole class
private final String accountNumber; // one copy PER object
public BankAccount(String accountNumber, String owner) {
...
accountsCreated++;
}
public static long accountsCreated() { return accountsCreated; } // called on the class
}
BankAccount.accountsCreated();
static fields and methods belong to the class itself. Static methods have no this and can't access instance fields directly. Typical uses are constants (static final), factory methods (Money.of(...)) and utility functions (Math.max).public class Rectangle {
private final double width, height;
public Rectangle(double width, double height) {
if (width <= 0 || height <= 0) throw new IllegalArgumentException("Sides must be positive");
this.width = width;
this.height = height;
}
public Rectangle(double side) { // overloaded constructor: a square
this(side, side); // delegate to the main constructor (must be the first statement)
}
}
If you write no constructor, Java supplies a default no-argument one. Once you write any constructor, that default disappears. When a class has many optional parameters, a builder or static factory methods read better than many overloaded constructors.
An object whose state can't change after construction (all fields private final, no setters, and defensive copies of mutable inputs) is immutable. String, Integer and LocalDate are examples. Immutable objects are simpler to reason about, safe to share between threads, and ideal as map keys. Java records (record Point(int x, int y) {}) are a concise way to write them (see Records).
Interviewers often ask for the four principles of OOP. Each has its own lesson:
extends).Q: What's the difference between a class and an object?
A: A class is a definition: its fields, constructors and methods. It's written once, in code. An object is a runtime instance of a class with its own field values, created with new. There can be any number of objects of one class.
Q: What happens when you write new BankAccount(...)?
A: The JVM allocates memory on the heap for the object's fields (initialized to defaults: 0, false, null), runs the constructor (after any superclass constructor), and returns a reference to the new object, which is stored in the variable.
Q: Is Java pass-by-reference? A: No. Java is always pass-by-value, but for objects the value passed is a reference. A method can therefore mutate the object the caller sees, but reassigning the parameter to a different object has no effect on the caller's variable.
Q: Why make fields private if you add getters and setters anyway? A: Blind setters do undermine encapsulation. The point of private fields is that the class controls how its state changes: validation, invariants, and the freedom to change the internal representation. Expose behaviour-oriented methods, add getters only when needed, and add setters only where unrestricted change is legitimate.
Q: When should a method be static? A: When it doesn't depend on any particular object's state, for example pure utility functions, factory methods, or operations on arguments only. If it needs instance fields, or should be overridable for polymorphism, it should be an instance method.