Q11 of 40 · Core Java
Explain the four pillars of OOP with Java examples.
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
Listreference can point to anArrayListorLinkedList; the actualadd()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