Skip to main content
Java beginner Lesson 12 of 58

Introduction to Object-Oriented Programming in Java

Understand what OOP is, why it exists, and how Java organises code around classes and objects with real-world examples.

Object-Oriented Programming (OOP) is a paradigm that structures programs around objects — self-contained units that bundle data (fields) and behaviour (methods) together. Java was designed from day one as an OOP language, and understanding its model is the foundation for everything else in the language.

Why OOP?

Before OOP, procedural programs were a flat list of functions passing data around. As programs grew, this became hard to manage — data was shared freely, any function could change any value, and tracking down bugs meant reading thousands of lines of code. OOP solves this by grouping related data and logic into objects, which mirrors how we think about the real world and makes large systems far more manageable.

ConcernProceduralOOP
Data & logicSeparateBundled in objects
ReuseCopy-paste functionsInheritance & composition
ScaleGets messy fastManageable with encapsulation
Modelling realityAwkwardNatural

The Four Pillars

Java OOP rests on four core principles. Each one solves a real design problem — they are not just theoretical concepts but tools you will reach for every day.

  1. Encapsulation — hide internal state, expose a clean interface
  2. Inheritance — child classes reuse and extend parent classes
  3. Polymorphism — one interface, many implementations
  4. Abstraction — work with concepts, not implementation details

Each pillar has its own dedicated tutorial. This page focuses on the fundamentals: classes and objects.

Defining a Class

A class is a blueprint that describes what data an object holds and what it can do. It does not represent a specific car — it describes what every car has (fields) and can do (methods). The private keyword on fields is intentional: it forces all access through methods, which lets the class validate changes and maintain a consistent state.

// A class is a blueprint — not a specific car, but the description of all cars
public class Car {

    // Fields — the object's data (private so nothing outside the class can corrupt them)
    private String brand;
    private String model;
    private int year;
    private double fuelLevel;

    // Constructor — runs when you create an instance with 'new'
    public Car(String brand, String model, int year) {
        this.brand = brand;
        this.model = model;
        this.year = year;
        this.fuelLevel = 1.0; // full tank by default
    }

    // Methods — the object's behaviour
    public void refuel(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive.");
        fuelLevel = Math.min(1.0, fuelLevel + amount);
    }

    public void drive(double distance) {
        double consumption = distance * 0.08; // 8L per 100km
        if (consumption > fuelLevel) {
            throw new IllegalStateException("Not enough fuel.");
        }
        fuelLevel -= consumption;
        System.out.printf("Drove %.1fkm. Fuel remaining: %.1f%%%n", distance, fuelLevel * 100);
    }

    // Getters — controlled read access to private fields
    public String getBrand() { return brand; }
    public int getYear()     { return year;  }

    @Override
    public String toString() {
        return String.format("%d %s %s (fuel: %.0f%%)", year, brand, model, fuelLevel * 100);
    }
}

Creating and Using Objects

Every object is created with the new keyword, which allocates memory on the heap and calls the constructor. Each object is independent — changing one does not affect the other, even though they were both created from the same class.

public class Main {
    public static void main(String[] args) {

        // Create two independent Car objects from the same class
        Car tesla = new Car("Tesla", "Model 3", 2023);
        Car bmw   = new Car("BMW",   "M3",      2022);

        System.out.println(tesla); // 2023 Tesla Model 3 (fuel: 100%)
        System.out.println(bmw);   // 2022 BMW M3 (fuel: 100%)

        tesla.drive(50);  // Drove 50.0km. Fuel remaining: 96%
        bmw.drive(200);   // Drove 200.0km. Fuel remaining: 84%

        // Each object maintains its own independent state
        System.out.println(tesla.getBrand()); // Tesla
        System.out.println(bmw);              // 2022 BMW M3 (fuel: 84%)
    }
}

Multiple Constructors

Java allows constructor overloading — multiple constructors with different parameter signatures. This lets callers create objects with varying levels of detail. The common pattern is to have a full constructor handle all the logic and have shorter constructors delegate to it using this(...).

public class Rectangle {
    private final double width;
    private final double height;

    // Full constructor — handles all initialisation logic
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    // Square shortcut — delegates to the full constructor to avoid duplication
    public Rectangle(double side) {
        this(side, side); // calls the two-arg constructor
    }

    public double area()      { return width * height; }
    public double perimeter() { return 2 * (width + height); }

    @Override
    public String toString() {
        return String.format("Rectangle(%.1f x %.1f)", width, height);
    }
}

Rectangle rect   = new Rectangle(4.0, 6.0);
Rectangle square = new Rectangle(5.0);

System.out.println(rect.area());    // 24.0
System.out.println(square.area());  // 25.0

Static vs Instance Members

Instance members belong to each individual object — every Car has its own fuelLevel. Static members belong to the class itself and are shared across all instances — useful for counters, constants, and utility methods that do not depend on any specific object’s state.

public class Counter {
    private static int totalCreated = 0; // shared across ALL Counter objects
    private final int id;

    public Counter() {
        totalCreated++;          // increments the shared count
        this.id = totalCreated;  // each object gets the next id
    }

    public int getId()                  { return id; }
    public static int getTotalCreated() { return totalCreated; }
}

Counter a = new Counter();
Counter b = new Counter();
Counter c = new Counter();

System.out.println(a.getId());              // 1
System.out.println(b.getId());              // 2
System.out.println(Counter.getTotalCreated()); // 3 — called on the class, not an instance

The this Keyword

this refers to the current object instance. Its most common use is resolving the naming conflict between a constructor parameter and the field it initialises — without this, x = x would just assign the parameter to itself, leaving the field unchanged.

public class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x; // "this.x" = field, plain "x" = parameter
        this.y = y;
    }

    // Returns the distance to another point using the Pythagorean theorem
    public double distanceTo(Point other) {
        double dx = this.x - other.x;
        double dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

Point origin = new Point(0, 0);
Point point  = new Point(3, 4);
System.out.println(origin.distanceTo(point)); // 5.0

What’s Next

Now that you understand how classes and objects work, explore each OOP pillar in depth:

Frequently Asked Questions

What is the difference between a class and an object?
A class is a blueprint or template. An object is a concrete instance created from that blueprint. You can think of a class as the cookie-cutter and an object as the cookie.
Why use OOP instead of procedural programming?
OOP makes code easier to organise, reuse, and maintain. Each object encapsulates its own data and behaviour, which reduces complexity and makes large codebases manageable.
Is everything in Java an object?
Almost. Java has 8 primitive types (int, double, boolean, etc.) that are not objects. Everything else — including Strings, arrays, and all user-defined types — is an object.