Object Oriented Programming With JavaUnit 118 min read
Advanced OOP: Sealed Classes, Polymorphism, Wrapper Classes & Design Patterns
Unit 11 of Object Oriented Programming With Java explores sealed classes, polymorphism (runtime vs compile-time), wrapper classes (autoboxing/unboxing), and design patterns (Singleton, Factory, Observer) with real-world examples, code traces, and exam-focused comparisons.
TAKEAWAYS:
- Sealed classes restrict inheritance hierarchies to predefined subclasses, enabling exhaustive pattern matching (e.g.,
switchexpressions). - Polymorphism in Java manifests as method overriding (runtime) and overloading (compile-time), with key differences in behavior and invocation.
- Wrapper classes (e.g.,
Integer,Double) bridge primitive types to objects via autoboxing/unboxing, critical for collections and generics. - Design patterns (Singleton, Factory, Observer) solve recurring OOP problems like thread-safe instantiation or event handling.
- The
finalmodifier enforces immutability at class/method/field levels, contrasting withsealed(restrictive) andabstract(extensible) classes. - Real-world applications include Kathmandu traffic route optimization (Observer pattern) and Ncell’s thread-safe user session management (Singleton).
1. Sealed Classes: Restricting Inheritance Hierarchies
Sealed classes introduce a controlled form of inheritance where a class explicitly permits only specific subclasses. This enables exhaustive pattern matching (e.g., switch expressions) and improves type safety.
Key Features
- Permitted Subclasses: Use
permitsclause to list allowed subclasses. - Final/Abstract/Sealed Subclasses: Subclasses can be
final,sealed, ornon-sealed. - Exhaustive Switch: Compiler checks if all permitted subclasses are handled.
Example: Payment System
public sealed interface Payment permits CreditCardPayment, UPIPayment, CashPayment {
double processPayment(double amount);
}
public final class CreditCardPayment implements Payment { ... }
public sealed class UPIPayment implements Payment permits PhonePePayment, GooglePayPayment { ... }
public final class PhonePePayment extends UPIPayment { ... }
Visual: Sealed Class Hierarchy
classDiagram
class Payment {
<<sealed>>
+processPayment(amount: double)
}
class CreditCardPayment {
<<final>>
}
class UPIPayment {
<<sealed>>
+processPayment()
}
class PhonePePayment {
<<final>>
}
Payment <|-- CreditCardPayment
Payment <|-- UPIPayment
UPIPayment <|-- PhonePePaymentReal-World Use: Ncell’s Payment Gateway
Ncell’s app uses sealed classes to restrict payment methods to CreditCard, MobileWallet, or BankTransfer. This ensures exhaustive validation during checkout.
2. Polymorphism: Runtime vs Compile-Time
Polymorphism allows objects to take many forms. Java supports two types:
- Compile-Time (Method Overloading): Multiple methods with the same name but different parameters.
- Runtime (Method Overriding): Subclass provides a specific implementation of a superclass method.
Comparison Table
| Feature | Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|---|
| Mechanism | Method overloading | Method overriding |
| Resolution | At compile-time | At runtime (dynamic binding) |
| Example | add(int a, int b) vs add(double a, double b) |
Animal.speak() overridden in Dog |
| Performance | Faster (no runtime lookup) | Slightly slower (JVM lookup) |
Example: Shape Hierarchy
class Shape {
void draw() { System.out.println("Drawing shape"); }
}
class Circle extends Shape {
@Override
void draw() { System.out.println("Drawing circle"); }
}
class Square extends Shape {
@Override
void draw() { System.out.println("Drawing square"); }
}
Visual: Runtime Polymorphism Trace
sequenceDiagram
participant Shape as Shape (Reference)
participant Circle as Circle (Object)
participant Square as Square (Object)
Shape->>Circle: draw() called
Circle->>Circle: "Drawing circle" executed
Shape->>Square: draw() called
Square->>Square: "Drawing square" executedReal-World Use: Daraz’s Order Processing
Daraz uses runtime polymorphism to handle different order types (ElectronicsOrder, GroceriesOrder). Each subclass overrides process() to apply specific business logic (e.g., discounts).
3. Wrapper Classes and Autoboxing/Unboxing
Wrapper classes convert primitive types (int, double) to objects (Integer, Double), enabling use in collections (e.g., ArrayList<Integer>).
Key Wrapper Classes
| Primitive | Wrapper Class | Autoboxing Example |
|---|---|---|
int |
Integer |
List<Integer> list = new ArrayList<>(); list.add(5); |
double |
Double |
Double d = 3.14; |
char |
Character |
Character c = 'A'; |
Example: Autoboxing/Unboxing
int num = 10; // Primitive
Integer numObj = num; // Autoboxing (int → Integer)
int numPrim = numObj; // Unboxing (Integer → int)
Visual: Autoboxing Flow
flowchart TD
A["int num = 10"] -->|"Autoboxing"| B["Integer numObj"]
B -->|"Unboxing"| C["int numPrim"]Real-World Use: eSewa’s Transaction Logs
eSewa stores transaction amounts as BigDecimal (wrapper for double) to avoid floating-point precision errors in financial calculations.
4. Design Patterns: Singleton, Factory, Observer
Design patterns provide reusable solutions to common OOP problems.
A. Singleton Pattern
Ensures a class has only one instance and provides a global point of access. Example: Database Connection
public class Database {
private static Database instance;
private Database() {} // Private constructor
public static Database getInstance() {
if (instance == null) instance = new Database();
return instance;
}
}
Visual: Singleton Lifecycle
stateDiagram-v2
[*] --> NoInstance: Initial State
NoInstance --> Instance: getInstance() called
Instance --> [*]: Instance usedB. Factory Pattern
Delegates instantiation to subclasses. Example: Vehicle Factory
interface Vehicle { void drive(); }
class Car implements Vehicle { public void drive() { ... } }
class Bike implements Vehicle { public void drive() { ... } }
class VehicleFactory {
public Vehicle getVehicle(String type) {
return switch(type) {
case "car" -> new Car();
case "bike" -> new Bike();
default -> throw new IllegalArgumentException();
};
}
}
C. Observer Pattern
Notifies observers (e.g., UI components) of state changes. Example: Stock Market Alerts
interface Observer { void update(String news); }
class NewsAgency {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) { observers.add(o); }
public void notifyObservers(String news) {
observers.forEach(o -> o.update(news));
}
}
Visual: Observer Pattern
classDiagram
class NewsAgency {
+addObserver(o: Observer)
+notifyObservers(news: String)
}
class Observer {
<<interface>>
+update(news: String)
}
class StockApp {
-update(news: String)
}
NewsAgency "1" --> "*" Observer : notifies
Observer <|-- StockAppReal-World Use: Pathao’s Ride Updates
Pathao uses the Observer pattern to notify drivers of new ride requests in real-time.
Exam Tip
- Sealed Classes: Always list permitted subclasses in the
permitsclause. Exhaustiveswitchis a common exam question. - Polymorphism: Differentiate between overloading (compile-time) and overriding (runtime). Use UML diagrams to show inheritance.
- Wrapper Classes: Remember autoboxing/unboxing rules. Collections require wrapper types (e.g.,
List<Integer>). - Design Patterns:
- Singleton: Private constructor + static
getInstance(). - Factory: Use
switchorif-elsefor type-based instantiation. - Observer: Focus on the
update()method and observer registration.
- Singleton: Private constructor + static
- Code Traces: For polymorphism, trace method calls step-by-step (e.g.,
Shapereference pointing toCircleobject). - Real-World Links: Connect sealed classes to payment systems, polymorphism to order processing, and design patterns to apps like Pathao or eSewa.
Private constructor and static instance (Image: Hpesoj00, CC BY-SA 4.0, via Wikimedia Commons)
Based on the TU BITM syllabus for Object Oriented Programming With Java (IT234), unit 11.
Discussion
Loading…