Java Academy Logo

The Four Pillars of OOP in Java

Object-Oriented Programming (OOP) is built on four fundamental pillars that help us write organized, reusable, and maintainable code:

  • Encapsulation: Wrapping data and methods together, hiding internal details
  • Inheritance: Allowing classes to inherit features from other classes
  • Abstraction: Showing only essential features, hiding implementation
  • Polymorphism: Same method name, different behaviors

1. Encapsulation

Encapsulation is the process of wrapping data and methods together into a single unit. It acts as a protective shield that prevents data from being accessed by code outside this shield.

Key Points:

  • Variables inside a class are hidden from other classes
  • Data can only be accessed through member functions
  • Also known as "data hiding"

Real-Life Analogy: Building Security

A building has private rooms that can only be accessed through the main door with the right key. You can't directly enter secret rooms without proper authorization.

encapsulation.java
class Building {
    // private data (hidden rooms)
    private String secretRoom = "Hidden Archive";
    private String officeRoom = "Manager Office";

    // public method (main door)
    public String enter(String key) {
        if (key.equals("office")) {
            return officeRoom;
        } else if (key.equals("secret")) {
            return "Access Denied! Secret room is protected.";
        } else {
            return "Invalid key!";
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Building b = new Building();

        // trying to enter rooms
        System.out.println(b.enter("office"));  // allowed
        System.out.println(b.enter("secret"));  // not allowed
    }
}

Output:

Manager OfficeAccess Denied! Secret room is protected.

2. Inheritance

Inheritance is the mechanism by which one class is allowed to inherit the features (fields and methods) of another class. We achieve inheritance using the extends keyword.

Superclass (Parent)

The class that gives its features to another class

Subclass (Child)

The class that receives features from another class

✔ Reusability

Inheritance supports code reusability. When creating a new class, if an existing class includes some needed code, we can derive the new class from the existing one instead of writing everything from scratch.

inheritance.java
// Parent class
class Building {
    void showBuilding() {
        System.out.println("This is a building with walls and doors.");
    }
}

// Child class
class House extends Building {
    void showHouse() {
        System.out.println("This house has a kitchen and bedrooms.");
    }
}

// Another child class
class Office extends Building {
    void showOffice() {
        System.out.println("This office has meeting rooms and workspaces.");
    }
}

public class Main {
    public static void main(String[] args) {
        House h = new House();
        h.showBuilding(); // inherited from Building
        h.showHouse();    // its own feature

        Office o = new Office();
        o.showBuilding(); // inherited
        o.showOffice();   // its own feature
    }
}

3. Abstraction

Abstraction means hiding the internal working details and showing only the important features to the user. It lets us focus on what an object does, not how it does it.

Implementation:

  • Abstract classes
  • Interfaces (can provide 100% abstraction)

Real-Life Analogy: Light Switch

When you flip a light switch, you don't need to know about the complex electrical wiring behind the wall. You just need to know that flipping the switch turns the light on or off.

abstraction.java
// Abstract class (hides how the light works)
abstract class Building {
    // abstract method: what the user can do
    abstract void turnOnLights();
}

// Concrete class: shows WHAT it does, hides HOW it does it
class House extends Building {
    @Override
    void turnOnLights() {
        System.out.println("Lights are turned on in the house.");
    }
}

public class Main {
    public static void main(String[] args) {
        Building b = new House();
        b.turnOnLights();   // user just uses the feature
    }
}

4. Polymorphism

Polymorphism means "many forms". It allows us to perform a single action in different ways. In Java, there are two main types of polymorphism:

Method Overloading

Compile-time polymorphism

Method Overriding

Runtime polymorphism

Method Overloading (Compile-Time Polymorphism)

Multiple methods share the same name but have different parameters in the same class. The compiler knows which method to call before running the program.

overloading.java
class Building {

    // open door with a key
    void openDoor(String key) {
        System.out.println("Door opened with a key.");
    }

    // open door with a password
    void openDoor(int password) {
        System.out.println("Door opened with a password.");
    }

    // open door with a card
    void openDoor(String card, int code) {
        System.out.println("Door opened with a card and code.");
    }
}

public class Main {
    public static void main(String[] args) {
        Building b = new Building();

        b.openDoor("metalKey");      // chooses key method
        b.openDoor(1234);            // chooses password method
        b.openDoor("accessCard", 99); // chooses card method
    }
}

Output:

Door opened with a key.Door opened with a password.Door opened with a card and code.

Method Overriding (Runtime Polymorphism)

A child class provides its own implementation of a method that already exists in the parent class. The method has the same name, return type, and parameters. The Java compiler can't identify which method to call until the program runs, because objects are created at runtime.

overriding.java
// Parent class
class Building {
    void openDoor() {
        System.out.println("Opening the building door in a standard way.");
    }
}

// Child class 1
class House extends Building {
    @Override
    void openDoor() {
        System.out.println("Opening the house door with a key.");
    }
}

// Child class 2
class Office extends Building {
    @Override
    void openDoor() {
        System.out.println("Opening the office door with a password.");
    }
}

public class Main {
    public static void main(String[] args) {
        Building b1 = new House();
        Building b2 = new Office();

        b1.openDoor();  // calls House version
        b2.openDoor();  // calls Office version
    }
}

Output:

Opening the house door with a key.Opening the office door with a password.

Advantages of OOP over Procedural Programming

✔ Reusability

Create reusable components using objects and classes, leading to less duplication

✔ Clear Structure

Provides logical organization, making code easier to understand and maintain

✔ DRY Principle

Don't Repeat Yourself - common functionalities in one place, reducing redundancy

✔ Faster Development

Reuse existing code and create modular components for quicker application development

Disadvantages of OOP

✘ Learning Curve

Concepts like classes, objects, and inheritance can be confusing for beginners

✘ Overhead for Small Programs

Using OOP for simple programs may require more code than necessary

✘ Debugging Complexity

Code divided into classes and layers can make finding and fixing bugs more time-consuming

✘ Memory Usage

Creates many objects, which can use more memory compared to procedural programming