Java is an object-oriented language: you model your program as objects that hold data and know how to act on it. A class is the blueprint; an object is a thing built from it. BankAccount is a class; Alice’s account and Bob’s account are two objects.
A class has fields, a constructor and methods
public class BankAccount {
private final String owner; // fields: the object's data
private double balance;
public BankAccount(String owner, double openingBalance) { // constructor
this.owner = owner;
this.balance = openingBalance;
}
public void deposit(double amount) { // method: behaviour
balance += amount;
}
}
Create objects with new:
BankAccount alice = new BankAccount("Alice", 100);
alice.deposit(25);
this
Inside a constructor or method, this means “the current object”. this.owner = owner copies the parameter owner into the field owner — they have the same name, so this. tells them apart.
Encapsulation: protect your data
Fields are private, so code outside the class can’t change them directly. Instead, the class offers methods that enforce its rules:
public boolean withdraw(double amount) {
if (amount > balance) return false; // the rule lives in one place
balance -= amount;
return true;
}
public double getBalance() { return balance; } // read-only access
Nobody can set the balance to a negative number by accident, because there’s no way to reach it except through these methods. Validate in the constructor too — an object should never exist in an invalid state.
final fields
private final String owner; can be set once (in the constructor) and never changed. Prefer final wherever possible: objects that don’t change are easier to reason about and safe to share.
static: belongs to the class, not an object
private static int accountsOpened = 0; // one counter shared by all accounts
Every account has its own balance, but there’s only one accountsOpened. Static methods (like main) can be called without creating an object: BankAccount.getAccountsOpened().
toString(): a readable description
@Override
public String toString() {
return owner + ": " + String.format("%.2f", balance);
}
System.out.println(alice) now prints Alice: 125.00 instead of something like BankAccount@1b6d3586.
Tip: For classes that are just data (no rules beyond holding values), Java 16+ has records, which write the constructor, getters, equals, hashCode and toString for you.
The full example
// BankAccount.java — classes, objects, constructors, encapsulation, static members.
public class BankAccount {
private static int accountsOpened = 0; // shared by all accounts
private final String owner; // set once, never changes
private double balance; // hidden: change it only through methods
public BankAccount(String owner, double openingBalance) {
if (openingBalance < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative");
}
this.owner = owner;
this.balance = openingBalance;
accountsOpened++;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
balance += amount;
}
public boolean withdraw(double amount) {
if (amount > balance) {
return false; // not enough money
}
balance -= amount;
return true;
}
public double getBalance() { return balance; }
public String getOwner() { return owner; }
public static int getAccountsOpened() { return accountsOpened; }
@Override
public String toString() {
return owner + ": " + String.format("%.2f", balance);
}
public static void main(String[] args) {
BankAccount alice = new BankAccount("Alice", 100);
BankAccount bob = new BankAccount("Bob", 50);
alice.deposit(25);
boolean ok = bob.withdraw(80);
System.out.println(alice); // Alice: 125.00
System.out.println(bob + " (withdraw ok? " + ok + ")");
System.out.println("Accounts: " + BankAccount.getAccountsOpened());
}
}
Try it yourself
Add a transferTo(BankAccount other, double amount) method that moves money from this account to another, only if there’s enough balance. Return true if it worked.
Show a solution
public boolean transferTo(BankAccount other, double amount) {
if (!withdraw(amount)) {
return false;
}
other.deposit(amount);
return true;
}
Reusing withdraw and deposit keeps every rule in one place.
Key takeaways
- A class is a blueprint;
newcreates objects from it. - Keep fields
private(andfinalwhere you can); expose behaviour through methods. thisis the current object;staticmembers belong to the class.- Override
toString()for readable output — or use a record for pure data.