Q11 of 40 · Core Java

Explain the four pillars of OOP with Java examples.

Core JavaJunioroopencapsulationinheritancepolymorphismabstractionjava-fundamentals

Short answer

Short answer: Encapsulation hides internal state behind methods. Inheritance lets a subclass reuse and extend a parent class. Polymorphism lets a reference of a parent type behave differently based on the actual subclass. Abstraction exposes only essential behaviour, hiding implementation details behind interfaces or abstract classes.

Detail

Encapsulation — bundling state and the methods that operate on it, hiding internal details. Fields are private; access goes through getters/setters or richer behavioural methods. In Page Objects, the locator strings are private; the test only calls loginPage.login("alice", "pw"). This means you can change the selector without changing any test.

Inheritance — a class acquires the fields and methods of its parent via extends. LoginPage extends BasePage gets the page reference and navigate() method without redeclaring them. Prefer composition over inheritance when the relationship isn't a true "is-a" — it keeps code more flexible.

Polymorphism — many forms. Two main types:

  • Compile-time (static): method overloading — same name, different parameter list.
  • Runtime (dynamic): method overriding — a List reference can point to an ArrayList or LinkedList; the actual add() implementation dispatched depends on the runtime type. This is the heart of the strategy and template-method patterns.

Abstraction — exposing "what" while hiding "how". An interface declares List.add(); the caller doesn't care if it's an ArrayList or LinkedList. In test automation, a UserService interface in a test helper can be backed by a real HTTP client in integration tests and a in-memory stub in unit tests — callers are identical.

All four work together. Encapsulation protects state; abstraction hides complexity; inheritance and polymorphism enable reuse and substitutability.

// EXAMPLE

FourPillars.java

// ENCAPSULATION — private state, public behaviour
class TestUser {
    private final String email;
    private String sessionToken;

    TestUser(String email) { this.email = email; }

    void authenticate(AuthService auth) {
        this.sessionToken = auth.login(email, "password"); // state hidden
    }
    boolean isAuthenticated() { return sessionToken != null; }
}

// INHERITANCE — subclass reuses parent
abstract class BasePage {
    protected final Page page;
    BasePage(Page page) { this.page = page; }
    abstract String pageTitle();
}
class CheckoutPage extends BasePage {
    CheckoutPage(Page page) { super(page); }
    @Override public String pageTitle() { return "Checkout"; }
}

// POLYMORPHISM — runtime dispatch via overriding
List<String> list = new ArrayList<>();  // ArrayList IS-A List
list.add("a");                          // dispatches to ArrayList.add()
list = new LinkedList<>();              // same reference, different impl
list.add("b");                          // dispatches to LinkedList.add()

// ABSTRACTION — caller sees interface, not implementation
interface UserRepository {
    User findById(long id);
}
class HttpUserRepository implements UserRepository { ... }   // production
class InMemoryUserRepository implements UserRepository { ... } // tests

// WHAT INTERVIEWERS LOOK FOR

Correct definition and distinct example for each pillar, with the distinction between compile-time and runtime polymorphism. Strong answers connect each pillar to test automation — Page Object for encapsulation, BasePage for inheritance, stub/fake for abstraction.

// COMMON PITFALL

Conflating abstraction and encapsulation. Encapsulation is about protecting internal state; abstraction is about simplifying the interface so callers don't need to know about implementation. They often work together but solve different problems.