OOP Principles¶
OOP is built on four pillars: Encapsulation (bundling data and methods, hiding internals), Inheritance (creating new classes from existing ones), Polymorphism (same interface, different implementations), and Abstraction (exposing only essential features). These principles promote code reuse, modularity, and maintainability.
Encapsulation¶
Encapsulation bundles data (fields) and behavior (methods) into a single unit (class) and hides internal state behind well-defined interfaces. Access modifiers (private, protected, public) control visibility, and getters/setters expose only what's necessary. This prevents invalid state and makes refactoring safe.
Deep Dive: Access Modifiers
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
| default (no modifier) | ✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
Deep Dive: Encapsulation in Practice
public class BankAccount {
private double balance; // Hidden from outside
public void deposit(double amount) {
if (amount > 0) { // Validation enforces invariants
this.balance += amount;
}
}
public double getBalance() {
return this.balance; // Read-only access
}
// No setter — balance can only change via deposit/withdraw
}
Why it matters:
- Prevents invalid state (e.g., negative balance without explicit logic)
- Allows changing internal implementation without affecting callers
- Enforces business rules in one place
Inheritance¶
Inheritance creates new classes from existing ones using extends (classes) or implements (interfaces). The subclass inherits methods and fields from the parent, enabling code reuse. However, deep inheritance hierarchies create tight coupling. Rule of thumb: Favor composition over inheritance (Effective Java, Item 18).
Deep Dive: Inheritance vs Composition
Inheritance ("is-a") creates tight coupling — changes in parent break children.
Composition ("has-a") is more flexible — delegate behavior to collaborators.
// Inheritance — tightly coupled
class Dog extends Animal { }
// Composition — loosely coupled, preferred
class Dog {
private final Legs legs; // Has legs (delegates movement)
private final Tail tail; // Has tail (delegates wagging)
}
When to use inheritance:
- True "is-a" relationships (e.g.,
ArrayListis aList) - Template Method pattern (shared algorithm structure)
- Framework extension points designed for it
When to use composition:
- "Has-a" relationships
- Behaviors that need to be swapped at runtime
- When you need to combine behaviors from multiple sources
Deep Dive: The Diamond Problem
Java doesn't support multiple class inheritance (a class can't extend two classes) because of ambiguity — which parent method should be used?
// Java solves this with interfaces + default methods
interface Flyable {
default void move() { System.out.println("Flying"); }
}
interface Swimmable {
default void move() { System.out.println("Swimming"); }
}
class Duck implements Flyable, Swimmable {
@Override
public void move() {
Flyable.super.move(); // Explicitly choose which default
}
}
Polymorphism¶
Polymorphism lets the same interface have different implementations. Compile-time (static) polymorphism is achieved through method overloading. Runtime (dynamic) polymorphism is achieved through method overriding — the actual method called is determined by the object's runtime type, not the reference type. This is how Spring DI works: code programs to interfaces, the framework injects concrete implementations.
Deep Dive: Method Overloading vs Overriding
| Feature | Overloading | Overriding |
|---|---|---|
| When resolved | Compile-time | Runtime |
| Same method name | Yes | Yes |
| Same parameters | No (must differ) | Yes (must match) |
| Same return type | Not required | Required (or covariant) |
| Annotation | None | @Override |
| Static methods | Can overload | Cannot override (hidden) |
// Overloading — same name, different parameters
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
// Overriding — same signature, different class
class Animal {
void speak() { System.out.println("..."); }
}
class Dog extends Animal {
@Override
void speak() { System.out.println("Woof!"); }
}
// Runtime polymorphism in action
Animal animal = new Dog();
animal.speak(); // Prints "Woof!" — Dog's version is called
Abstraction¶
Abstraction hides complexity and exposes only essential features. Achieved via abstract classes (partial implementation, can have state) and interfaces (pure contract, no state). Abstract classes are "is-a" with shared code; interfaces define capabilities ("can-do"). Since Java 8, interfaces can have default and static methods.
Deep Dive: Abstract Class vs Interface
| Feature | Abstract Class | Interface |
|---|---|---|
| Methods | Abstract + concrete | Abstract + default + static |
| Fields | Instance variables | Constants only (public static final) |
| Constructors | Yes | No |
| Multiple inheritance | No (single extends) | Yes (multiple implements) |
| Access modifiers | Any | Public (methods are public by default) |
| Use case | Shared code + state | Define a capability/contract |
// Abstract class — shared state and behavior
abstract class Shape {
protected String color;
Shape(String color) { this.color = color; }
abstract double area(); // Must be implemented by subclass
String getColor() { return color; } // Shared behavior
}
// Interface — capability contract
interface Drawable {
void draw();
default void drawWithBorder() { // Default method (Java 8+)
draw();
System.out.println("Drawing border");
}
}
class Circle extends Shape implements Drawable {
private double radius;
Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override double area() { return Math.PI * radius * radius; }
@Override public void draw() { System.out.println("Drawing circle"); }
}
Common Interview Questions¶
Common Interview Questions
- What are the four pillars of OOP?
- Explain the difference between abstraction and encapsulation.
- When would you use inheritance vs composition?
- What is the diamond problem? How does Java solve it?
- Can you achieve polymorphism without inheritance? (Yes — interfaces)
- What is the difference between method overloading and overriding?
- When would you use an abstract class vs an interface?
- What is covariant return type?