What you'll learn

  • The difference between a class and an object
  • Fields, constructors and methods
  • Encapsulation with private fields and getters
  • this, static and toString()

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; new creates objects from it.
  • Keep fields private (and final where you can); expose behaviour through methods.
  • this is the current object; static members belong to the class.
  • Override toString() for readable output — or use a record for pure data.

Example files