OOP Basics in Java: Classes, Objects and the Four Pillars with Runnable Examples

Connect Java's OOP syntax to design decisions with a complete Account example. Trace two objects through validation, overriding and runtime dispatch to exact output.

KnowledgeGate Team

Exam prep & CS education

Updated 14 Oct 20265 min read

You can write loops and methods, yet class, new, extends and @Override may still look like disconnected Java syntax. Each keyword changes one object model: define a type, create instances, specialise behaviour and verify an override. Two account objects then move through validation and runtime dispatch to exact output.

OOP basics in Java: the class, object and reference mental model

Oracle's Java Tutorials on object-oriented concepts describe a class as the blueprint from which objects are created, and an object as a bundle of state and behaviour. That source covers JDK 8, so use it for these core definitions, not for claims about current Java releases.

Here, Account declares state (owner and balance) and behaviour (deposit(), withdraw() and applyMonthEnd()). new SavingsAccount("Asha", 10000, 5) creates one object reached through the asha reference. ravi points to a separate CurrentAccount, so their balances are independent.

Term

Meaning here

Class

Account, the shared type or blueprint

Object

One SavingsAccount or CurrentAccount instance at runtime

Reference

asha or ravi, a variable that points to an object

State

Owner, balance, interest percentage or monthly fee

Behaviour

Deposit, withdraw and month-end processing

The Coding & Skills category places this Java foundation inside a wider language and DSA learning path.

Java classes and objects: build the base Account abstraction

Start OopDemo.java with an abstract base class:

java
abstract class Account {
    private final String owner;
    private int balance;

    Account(String owner, int openingBalance) {
        if (openingBalance < 0) {
            throw new IllegalArgumentException("Opening balance cannot be negative");
        }
        this.owner = owner;
        this.balance = openingBalance;
    }

    public String getOwner() {
        return owner;
    }

    public int getBalance() {
        return balance;
    }

    public void deposit(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        balance += amount;
    }

    public void withdraw(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        if (amount > balance) {
            throw new IllegalArgumentException("Insufficient balance");
        }
        balance -= amount;
    }

    abstract void applyMonthEnd();
}

The class is abstract because every account shares identity and safe balance operations, but no month-end rule fits every account. Its abstract method requires that operation without inventing a universal implementation.

Account class blueprint beside two objects: Asha's SavingsAccount holding 10000 and Ravi's CurrentAccount holding 8000.

Encapsulation in Java: protect state through methods

Encapsulation matters because it protects an invariant: a balance changes only through checked operations. Client code cannot bypass those checks because balance is private. This deliberately commented line would not compile:

java
// asha.balance = 999999; // balance has private access in Account

Asha begins at 10000. deposit(2500) makes the balance 10000 + 2500 = 12500. Ravi begins at 8000. withdraw(1500) makes his balance 8000 - 1500 = 6500.

The checks define predictable failures. asha.deposit(-100) throws Amount must be positive. After Ravi reaches 6500, ravi.withdraw(9000) throws Insufficient balance, leaving his state at 6500. Getters allow reading, but no public setter bypasses the rules.

Inheritance and overriding in Java: specialise the shared account type

Now add two subclasses to the same file:

java
class SavingsAccount extends Account {
    private final int interestPercent;

    SavingsAccount(String owner, int openingBalance, int interestPercent) {
        super(owner, openingBalance);
        this.interestPercent = interestPercent;
    }

    @Override
    void applyMonthEnd() {
        int interest = getBalance() * interestPercent / 100;
        deposit(interest);
    }
}

class CurrentAccount extends Account {
    private final int monthlyFee;

    CurrentAccount(String owner, int openingBalance, int monthlyFee) {
        super(owner, openingBalance);
        this.monthlyFee = monthlyFee;
    }

    @Override
    void applyMonthEnd() {
        withdraw(monthlyFee);
    }
}

Both subclasses inherit getOwner(), getBalance(), deposit() and withdraw(). They cannot access private balance directly, so they reuse the safe methods.

For Asha, interest is 12500 * 5 / 100 = 625, and the new balance is 12500 + 625 = 13125. For Ravi, the fee produces 6500 - 200 = 6300. Overriding supplies a new implementation with the same signature. Overloading uses the same method name with a different parameter list.

Abstraction and runtime polymorphism: run one loop over two behaviours

Complete the file with the public class:

java
public class OopDemo {
    public static void main(String[] args) {
        SavingsAccount asha = new SavingsAccount("Asha", 10000, 5);
        CurrentAccount ravi = new CurrentAccount("Ravi", 8000, 200);

        asha.deposit(2500);
        ravi.withdraw(1500);

        Account[] accounts = {asha, ravi};
        int total = 0;

        for (Account account : accounts) {
            account.applyMonthEnd();
            System.out.println(account.getOwner() + " -> " + account.getBalance());
            total += account.getBalance();
        }

        System.out.println("Total -> " + total);
    }
}

The Account type exposes an operation while hiding its algorithm. That is abstraction. The JVM dispatches the call to the actual object's override. That is polymorphism.

Runtime type

Balance before month end

Dispatched rule

Final balance

SavingsAccount

12500

Add 12500 * 5 / 100 = 625

13125

CurrentAccount

6500

Subtract 200

6300

The exact output is:

Code
Asha -> 13125
Ravi -> 6300
Total -> 19425

The total is 13125 + 6300 = 19425.

One applyMonthEnd() call dispatching to Asha's 625 interest and Ravi's 200 fee, ending at 13125, 6300 and a 19425 total.

OOP basics in Java: common errors and what fixes them

Without super(owner, openingBalance), Java cannot initialise the base state because Account has no zero-argument constructor. Call its available constructor. A subclass also cannot read private balance; it must use getBalance().

Changing applyMonthEnd() to applyMonthEnds() leaves the abstract method unimplemented. Keep @Override to catch that typo, and do not reduce visibility. new Account(...) fails because an abstract class cannot be instantiated.

Inheritance must model an is-a relationship. A SavingsAccount is an Account. An InterestCalculator merely uses account data, so extending Account only to reuse code creates a false model. Prefer composition for a uses-a relationship.

OOP basics in interviews and exams: trace, explain and extend

Trace this check: Account a = new SavingsAccount("Neha", 4000, 10); a.deposit(1000); a.applyMonthEnd(); System.out.println(a.getBalance());. The balance becomes 4000 + 1000 = 5000; runtime dispatch adds 5000 * 10 / 100 = 500; the line prints 5500. The reference type does not change the runtime object. Identify the class, object, reference, inherited method and override.

Then make three changes:

  1. Change Asha's rate from 5 to 4. Interest becomes 12500 * 4 / 100 = 500, Asha ends at 13000, and the total is 13000 + 6300 = 19300.

  2. Add PremiumAccount, whose month-end rule deposits a fixed 300 into an opening balance of 5000. Its result is 5000 + 300 = 5300.

  3. Call withdraw(5301) on that object. Expect Insufficient balance, with the state unchanged at 5300.

For field hiding, compile-time binding and interview traps, use OOPs Concepts in Java: Worked Interview Questions. OopDemo.java instead follows two object states through encapsulation, inheritance, abstraction and runtime dispatch. For a wider preparation map, see Technical Interview: OS, DBMS, CN & OOP Prep.

OOP basics in Java: the short version and next step

A class is a blueprint, an object is an instance, encapsulation guards state, inheritance reuses a valid base, abstraction exposes an operation, and polymorphism selects its runtime implementation. The Account[] loop brings all four pillars together.

If you want a language-first route from fundamentals through OOP, follow Complete Java. If you are preparing for coding assessments across languages and placement practice, Coding For Placements is the broader route.

Type the complete file instead of pasting it. Verify the three output lines, then implement PremiumAccount and reach 5300 without changing the polymorphic loop in OopDemo.