Phase 2
Object-Oriented Programming (OOP) & Modern Class Design
📂 Module 1_ Problem Solving & Java programming \\ Module 2_ Java LTS Features
2.1 Classes, Objects, Constructors, this Keyword & Chaining
📖 Cay S. Horstmann • Core Java Volume I — Chapter 5, Section 5.1 ("Inheritance & Dynamic Binding")
“Dynamic binding has a very important property: it makes programs extensible without needing to modify existing code. When the virtual machine calls a method, it looks up the method table for the actual class of the implicit parameter.”
- vtable LawHotSpot computes a flat method table per loaded class for instant O(1) dynamic method dispatch.
⚡ Pro-Tip: Constructor Chaining Rule
this() or super() MUST be the very first statement in a constructor body. Base state must be initialized before subclass field initialization!
💡 Real-World Analogy: Blueprint vs Physical Building
• Class (The Architectural Blueprint): Drawn on paper. It defines that a house has 3 bedrooms, 2 bathrooms, and a front door. You cannot live inside a blueprint! It consumes almost no physical space.
• Object (The Physical House on 42 Elm Street): A tangible, physical reality built from that blueprint. It takes up actual space on land (Heap memory) and has its own distinct address (reference pointer). You can build 500 distinct houses from one blueprint.
• Object (The Physical House on 42 Elm Street): A tangible, physical reality built from that blueprint. It takes up actual space on land (Heap memory) and has its own distinct address (reference pointer). You can build 500 distinct houses from one blueprint.
📖 Why Constructor Chaining via this() Exists
Instead of duplicating field initialization logic across 5 overloaded constructors, Java allows one constructor to call another using
CRITICAL COMPILER RULE: The call to
this(args).
CRITICAL COMPILER RULE: The call to
this() (or super()) MUST be the very first statement in the constructor body. You cannot execute custom logic before the parent or primary constructor establishes the object's fundamental state!
📂 Module 1.../Account.java — Constructor Chaining & Initialization Sequence
package com.accenture.ltt.oop;
public class Account {
private final String accountId;
private double balance;
private static double defaultInterestRate;
// 1. Static Initialization Block: Runs ONCE when class is first loaded by ClassLoader
static {
defaultInterestRate = 0.045; // 4.5% interest
System.out.println("Step 1: Static block executed (Only once per class!)");
}
// 2. Instance Initialization Block: Runs before EVERY constructor
{
System.out.println("Step 2: Instance initialization block executed");
}
// 3. Overloaded Constructor 1: Takes only accountId, defaults balance to 0.0
public Account(String accountId) {
this(accountId, 0.0); // Chaining to Constructor 2! MUST BE LINE 1
System.out.println("Step 4: Finished single-arg constructor for: " + accountId);
}
// 4. Overloaded Constructor 2: Master constructor initializing all fields
public Account(String accountId, double initialBalance) {
this.accountId = accountId; // 'this.accountId' resolves ambiguity with parameter 'accountId'
this.balance = initialBalance;
System.out.println("Step 3: Master constructor executed. ID: " + accountId + ", Balance: $" + balance);
}
}
👁 Object Initialization Execution Order (Very Common Interview Question!)
- 1st Static variables and static blocks: Execute once when the class is loaded by the ClassLoader into Metaspace.
-
2nd
Parent constructor:
super()executes to construct parent fields. - 3rd Instance variables & instance blocks: Execute in textual order top-to-bottom.
- 4th Constructor body: The remaining code in the invoked constructor completes.
2.2 Encapsulation, Inheritance & Why Java Bans Multiple Class Inheritance
💡 Real-World Analogy: The Diamond Problem
Imagine you have a biological Mom and a biological Dad, both of whom have an eye color gene.
• If Mom's gene says Blue and Dad's gene says Brown, your body has to follow complex genetic dominance rules.
• In C++, if Class D inherits from both Class B and Class C, and both override a method
• In Java, James Gosling and the designers said: "No ambiguity, no confusion." A class can extend EXACTLY ONE parent class. To inherit multiple behaviors, you use Interfaces because interfaces define contracts, not conflicting state!
• If Mom's gene says Blue and Dad's gene says Brown, your body has to follow complex genetic dominance rules.
• In C++, if Class D inherits from both Class B and Class C, and both override a method
doWork() from Class A, which doWork() does Class D inherit?
• In Java, James Gosling and the designers said: "No ambiguity, no confusion." A class can extend EXACTLY ONE parent class. To inherit multiple behaviors, you use Interfaces because interfaces define contracts, not conflicting state!
🖊 Architectural Comparison: The Diamond Problem in Classes vs Clean Interface Implementation
Mermaid Diagram
graph TD
subgraph ForbiddenInJava ["BANNED in Java: Multiple Class Inheritance"]
ClassA["Class A
+execute()"] ClassB["Class B extends A
Overrides: +execute() -> 'Option B'"] ClassC["Class C extends A
Overrides: +execute() -> 'Option C'"] ClassD["Class D extends B, C
COMPILE ERROR: Diamond Problem Ambiguity!"] ClassA --> ClassB ClassA --> ClassC ClassB -.->|ILLEGAL| ClassD ClassC -.->|ILLEGAL| ClassD end subgraph SolutionInJava ["LEGAL in Java: Multiple Interface Implementation"] Flyable["interface Flyable
void fly();"] Swimmable["interface Swimmable
void swim();"] Duck["class Duck implements Flyable, Swimmable
Provides concrete fly() and swim() methods"] Flyable --> Duck Swimmable --> Duck end
+execute()"] ClassB["Class B extends A
Overrides: +execute() -> 'Option B'"] ClassC["Class C extends A
Overrides: +execute() -> 'Option C'"] ClassD["Class D extends B, C
COMPILE ERROR: Diamond Problem Ambiguity!"] ClassA --> ClassB ClassA --> ClassC ClassB -.->|ILLEGAL| ClassD ClassC -.->|ILLEGAL| ClassD end subgraph SolutionInJava ["LEGAL in Java: Multiple Interface Implementation"] Flyable["interface Flyable
void fly();"] Swimmable["interface Swimmable
void swim();"] Duck["class Duck implements Flyable, Swimmable
Provides concrete fly() and swim() methods"] Flyable --> Duck Swimmable --> Duck end
2.2b Class Relationships: Composition (Strong Has-A) vs Aggregation (Weak Has-A)
💡 Real-World Analogy: Human Heart vs Car Passenger
• Composition (The Heart): A human body has a heart. If the human body dies, the heart dies with it (death relationship / lifetime dependency). In code, the parent class instantiates the child object inside its own constructor:
• Aggregation (The Passenger): A car has a passenger. If the car is driven to the junkyard and scrapped, the passenger simply steps out and continues living. The child object is created independently and passed into the parent via setter or constructor:
account = new Account();.
• Aggregation (The Passenger): A car has a passenger. If the car is driven to the junkyard and scrapped, the passenger simply steps out and continues living. The child object is created independently and passed into the parent via setter or constructor:
dept.setFaculty(faculty);.
📂 Module 1.../Demo100_Composition/src/com/accenture/ltt/CompositionTester.java
package com.accenture.ltt;
// Customer owns Account's entire lifecycle (Composition)
class Customer {
private String customerName;
private Account account;
public Customer() {
// Composition: Account object is instantiated INSIDE Customer constructor!
this.account = new Account();
this.account.setAccountNo(9001);
this.account.setAmount(5000.0);
}
public String getCustomerName() { return customerName; }
public void setCustomerName(String name) { this.customerName = name; }
public Account getAccount() { return account; }
}
public class CompositionTester {
public static void main(String[] args) {
Customer customer = new Customer();
customer.setCustomerName("John");
System.out.println("Customer Name: " + customer.getCustomerName());
System.out.println("Account Number: " + customer.getAccount().getAccountNo());
System.out.println("Balance Amount: $" + customer.getAccount().getAmount());
}
}
👁 Composition (Strong Has-A) Architecture Explained
-
Internal Instantiation
Line 9:
this.account = new Account();— The Customer directly instantiates the Account. External code cannot inject an arbitrary pre-existing account. -
Shared Lifecycle (Death Dependency)
When
customergoes out of scope and is collected by JVM Garbage Collection, its internalAccountinstance has no other references and is reclaimed from the Heap simultaneously.
📂 Module 1.../Demo101_Aggregation/src/com/accenture/ltt/AggregationTester.java
package com.accenture.ltt;
class Faculty {
private int facultyId;
private String facultyName;
public int getFacultyId() { return facultyId; }
public void setFacultyId(int id) { this.facultyId = id; }
public String getFacultyName() { return facultyName; }
public void setFacultyName(String name) { this.facultyName = name; }
}
class Department {
private String departmentName;
private Faculty faculty; // Weak reference: injected from outside!
public void setDepartmentName(String name) { this.departmentName = name; }
public String getDepartmentName() { return departmentName; }
public void setFaculty(Faculty f) { this.faculty = f; }
public Faculty getFaculty() { return faculty; }
}
public class AggregationTester {
public static void main(String[] args) {
Department department = new Department();
department.setDepartmentName("Computer Science");
// Faculty exists independently on the Heap BEFORE Department assigns it:
Faculty faculty = new Faculty();
faculty.setFacultyId(1001);
faculty.setFacultyName("Dr. Smith");
department.setFaculty(faculty); // Loose aggregation association
System.out.println("Dept: " + department.getDepartmentName() + " | Faculty: " + department.getFaculty().getFacultyName());
}
}
👁 Aggregation (Weak Has-A) Architecture Explained
-
Independent Existence
Lines 27-31:
Facultyis instantiated as a standalone object on the Heap. It does not rely onDepartmentto exist. -
Loose Association
If
department = nullis executed, theDepartmentobject is GC'd, but theFacultyobject remains safely alive and referenced on the Heap.
2.3 Polymorphism, Dynamic Method Dispatch & The Virtual Table (vtable)
💡 Real-World Analogy: The Universal Remote Control
Imagine you have a universal remote control labeled "EntertainmentDevice" with a button labeled "powerOn()".
• If your remote points at a Television and you press powerOn(), a bright screen turns on.
• If your remote points at a Surround Sound Receiver and you press powerOn(), music starts playing through the speakers.
Your hand holds the exact same remote reference (
• If your remote points at a Television and you press powerOn(), a bright screen turns on.
• If your remote points at a Surround Sound Receiver and you press powerOn(), music starts playing through the speakers.
Your hand holds the exact same remote reference (
EntertainmentDevice dev), but the physical device on the shelf (the Heap Object) dictates the actual behavior. That is Runtime Polymorphism!
📂 PolymorphicDispatchDemo.java
public class PolymorphicDispatchDemo {
abstract static class PaymentService {
abstract void process(double amount);
}
static class CreditCardPayment extends PaymentService {
@Override
void process(double amount) {
System.out.println("Charging $" + amount + " to Visa Card via Payment Gateway");
}
}
static class UpiPayment extends PaymentService {
@Override
void process(double amount) {
System.out.println("Transferring $" + amount + " instantly via UPI Virtual ID");
}
}
public static void main(String[] args) {
// Reference type is parent PaymentService, but heap object is CreditCardPayment
PaymentService service = new CreditCardPayment();
// Under the hood: JVM uses 'invokevirtual' opcode and consults the vtable of CreditCardPayment!
service.process(150.0);
// Swapping the heap instance to UpiPayment
service = new UpiPayment();
service.process(75.0);
}
}
👁 How Dynamic Method Dispatch Works Under the Hood (vtable)
-
Compile Time
The compiler checks: Does the class
PaymentServicehave a method calledprocess(double)? Yes. Compilation succeeds. The bytecode emitted isinvokevirtual. -
Runtime
The JVM inspects the actual object on the Heap that
servicepoints to. It finds that the object is aCreditCardPaymentinstance. -
vtable Lookup
Every class in memory has a Virtual Method Table (vtable) containing pointers to its method implementations. The JVM looks up the
processentry inCreditCardPayment's vtable and jumps directly to its machine code.
📂 Module 1.../Demo103_DynamicBinding/src/com/accenture/ltt/DynamicBindingTester.java
package com.accenture.ltt;
class Employee {
private String employeeName;
private int employeeId;
public Employee(String name, int id) {
this.employeeName = name;
this.employeeId = id;
}
public void displayDetails() {
System.out.println("Employee: " + employeeName + " | ID: " + employeeId);
}
}
class Manager extends Employee {
private int numberOfReportees;
public Manager(String name, int id, int reportees) {
super(name, id);
this.numberOfReportees = reportees;
}
@Override
public void displayDetails() {
System.out.println("Manager with " + numberOfReportees + " reportees!");
}
public int getNumberOfReportees() { return numberOfReportees; }
}
public class DynamicBindingTester {
public static void main(String[] args) {
// Reference is parent Employee, object on heap is Manager
Employee emp = new Manager("Cooper", 3001, 4);
// Dynamic Binding: At runtime, JVM calls Manager's displayDetails()!
emp.displayDetails();
// COMPILE ERROR if uncommented: Reference type 'Employee' does not declare getNumberOfReportees()!
// System.out.println(emp.getNumberOfReportees());
}
}
👁 Reference Type vs Heap Object Type Mechanics
-
Compiler Check (Left Hand Side)
The compiler checks the reference type
Employee. If a method likegetNumberOfReportees()is not defined onEmployee, the compiler rejects the code even though the underlying object is aManager! -
Runtime Execution (Right Hand Side)
When
emp.displayDetails()is invoked, the JVM looks at the heap object (Manager) and dynamically executes the overridden method.
2.4 Modern Interfaces: Why Java 8 Added Default Methods & Java 9 Added Private Methods
💡 Why Default Methods Were Added in Java 8 (The Backward Compatibility Crisis)
In Java 8, Oracle wanted to add the awesome new
The massive problem: In Java 7 and earlier, every class that implements an interface MUST provide an implementation for EVERY abstract method. Millions of Java enterprise projects had custom collections (like Hibernate, Apache Commons, Guava).
If Oracle added an abstract
The Solution: Default Methods (
stream() method to the existing java.util.Collection interface.
The massive problem: In Java 7 and earlier, every class that implements an interface MUST provide an implementation for EVERY abstract method. Millions of Java enterprise projects had custom collections (like Hibernate, Apache Commons, Guava).
If Oracle added an abstract
stream() method to Collection, every Java application in the world would break upon updating to Java 8!
The Solution: Default Methods (
default). It allows an interface to provide a default fallback implementation. Existing classes automatically inherit the default implementation without breaking!
📂 Module 2_ Java LTS Features/ModernOrderService.java
package com.accenture.ltt.oop;
@FunctionalInterface
public interface ModernOrderService {
// 1. Single Abstract Method (SAM): Makes it eligible for Lambdas!
boolean processOrder(String orderId, double amount);
// 2. Java 8 Default Method: Backward-compatible business logic
default void logAuditTrail(String orderId, String status) {
formatAndPrintAudit("AUDIT", orderId, status);
}
// 3. Java 8 Static Method: Utility helper bound to the interface
static boolean isValidOrderId(String orderId) {
return orderId != null && orderId.startsWith("ORD-");
}
// 4. Java 9 Private Method: Encapsulates shared logic between default methods
private void formatAndPrintAudit(String prefix, String orderId, String status) {
System.out.println("[" + prefix + "] Order " + orderId + " status updated to: " + status);
}
}
👁 Modern Interface Evolution Breakdown (Java 8 & 9)
-
SAM & @FunctionalInterface
Line 3:
@FunctionalInterfacetells the compiler this interface has exactly one abstract method (SAM:processOrder). This allows developers to instantiate it using clean Lambda expressions:(id, amt) -> amt > 0. -
Default Methods (Java 8)
Line 9:
default void logAuditTrail(...)— Provides a default body. Implementing classes inherit this method automatically without being forced to implement it. Critical for API library maintainers adding new features without breaking existing client code. -
Static Interface Methods (Java 8)
Line 14:
static boolean isValidOrderId(...)— Utility method directly attached to the interface namespace. Called asModernOrderService.isValidOrderId("ORD-1")without needing external utility helper classes likeOrderUtils. -
Private Interface Methods (Java 9)
Line 19:
private void formatAndPrintAudit(...)— Solves code duplication between multiple default methods inside the interface while keeping internal helper logic hidden from external consumers.
2.5 Modern Java: Records (Java 16) & Sealed Classes (Java 17)
| Modern Construct | Traditional Pain Point | How Modern Java Solves It |
|---|---|---|
| Records (Java 16+) | Writing 60 lines of boilerplate (private final fields, constructor, getters, equals(), hashCode(), toString()) just to carry data. |
A 1-line immutable data carrier: record UserDTO(Long id, String email) {}. The compiler writes all boilerplate automatically! |
| Sealed Classes (Java 17+) | You want a class hierarchy, but you want to tightly restrict WHO can subclass it, preventing third parties from creating rogue subclasses. | sealed class Shape permits Circle, Square {}. The compiler guarantees that only Circle and Square can ever inherit from Shape. |
📂 Module 2_ Java LTS Features/01_Java17_Demos/.../Product.java
package com.accenture.ltt.sampleclasses;
/**
* Java 17 Sealed Class: Restricts hierarchy to strictly permitted subclasses.
* Enables exhaustive pattern matching switch in modern backend systems.
*/
public sealed abstract class Product
permits ElectronicsProducts, KidsProducts, BeautyProducts {
abstract public String getProductDescription();
}
// Subclasses MUST explicitly declare their inheritance posture:
// - 'final': Cannot be extended any further
// - 'sealed': Extends the sealed hierarchy to another set of permitted subclasses
// - 'non-sealed': Opens the class up for general inheritance
public final class ElectronicsProducts extends Product {
@Override
public String getProductDescription() {
return "Certified Enterprise Electronic Device";
}
}
👁 Sealed Classes Compiler Rules
- permits Declares which specific subclasses can extend this class. All permitted subclasses must reside in the same package (or module).
- Exhaustiveness Enables compiler-enforced exhaustiveness in pattern matching switch statements. You don't need a default clause if every permitted subclass is handled!
📂 UserRecordDemo.java — Java 16 Immutable Records
public class UserRecordDemo {
// Modern Java 16 Record: A 1-line immutable data carrier!
// Automatically generates: private final fields, constructor, getters (id(), email()), equals(), hashCode(), toString()
public record UserDTO(Long id, String username, String email) {
// Compact Constructor: Used for validation without repeating 'this.x = x' boilerplate
public UserDTO {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email format: " + email);
}
}
}
public static void main(String[] args) {
UserDTO user = new UserDTO(101L, "john_doe", "john@accenture.com");
// Accessor methods use field name (id(), not getId())
System.out.println("User Email: " + user.email());
System.out.println("Auto toString(): " + user);
}
}
👁 Java 16 Records & Compact Constructor Breakdown
-
What Java Generates
Line 3:
public record UserDTO(Long id, String username, String email)— In just ONE line, the Java compiler automatically synthesizes: (1)private finalinstance fields, (2) Canonical constructor, (3) Getter methods named after fields (user.email()instead ofgetEmail()), (4) Field-basedequals()andhashCode(), and (5) FormattedtoString()! -
Immutability by Design
Records are shallowly immutable and implicitly
final(cannot be extended). Their state cannot be modified after construction, making them thread-safe DTOs for REST APIs and messaging queues. -
Compact Constructor
Lines 6-10:
public UserDTO { ... }— Special record constructor without a parameter list. Used strictly for input validation and normalization. Notice you do NOT writethis.email = email;— Java assigns fields automatically after the compact block finishes!
📂 OrderStatusEnumDemo.java — Enterprise Enums with Fields & Logic
public class OrderStatusEnumDemo {
public enum OrderStatus {
PENDING(100, "Awaiting Payment"),
PROCESSING(200, "Packing in Warehouse"),
SHIPPED(300, "In Transit"),
DELIVERED(400, "Delivered to Customer");
private final int statusCode;
private final String description;
// Enum constructor is always private
OrderStatus(int statusCode, String description) {
this.statusCode = statusCode;
this.description = description;
}
public int getStatusCode() { return statusCode; }
public String getDescription() { return description; }
public boolean isTerminal() {
return this == DELIVERED;
}
}
public static void main(String[] args) {
OrderStatus status = OrderStatus.PROCESSING;
System.out.println("Status Code: " + status.getStatusCode() + " -> " + status.getDescription());
System.out.println("Is complete? " + status.isTerminal());
}
}
👁 Enterprise Java Enums Architecture Explained
-
Under the Hood
In Java, an
enumis actually a full class that implicitly extendsjava.lang.Enum<E>. Each declared constant (PENDING,PROCESSING, etc.) is apublic static finalinstance created once during class loading. -
Custom Fields & Constructor
Lines 7-13: Enums can possess state and behaviors! The constructor is strictly
private(invoked only by the JVM when initializing constants). You can never callnew OrderStatus(...). - Thread-Safe Singletons Because enum constants are created by the ClassLoader during initialization, they are guaranteed by the JVM specification to be strictly thread-safe singletons and immune to reflection/serialization attacks.
-
Business Methods
Line 18:
isTerminal()— Enums can embed domain business rules, eliminating sprawlingif-elsechecks across your service layer.
📂 CloningDeepCopyDemo.java — Shallow Copy vs Deep Copy Proof
public class CloningDeepCopyDemo {
static class Address {
String city;
Address(String city) { this.city = city; }
}
static class Customer implements Cloneable {
String name;
Address address;
Customer(String name, Address address) {
this.name = name;
this.address = address;
}
// Shallow Copy: Default Object.clone() only copies primitive values & address references!
public Customer shallowCopy() throws CloneNotSupportedException {
return (Customer) super.clone();
}
// Deep Copy: Explicitly duplicates nested objects so modifications don't corrupt original!
public Customer deepCopy() {
return new Customer(this.name, new Address(this.address.city));
}
}
public static void main(String[] args) throws Exception {
Customer original = new Customer("Alice", new Address("New York"));
// 1. Shallow Copy Test
Customer shallow = original.shallowCopy();
shallow.address.city = "London";
System.out.println("Shallow copy changed original city to: " + original.address.city); // London! Shared object corrupted!
// 2. Deep Copy Test
Customer deep = original.deepCopy();
deep.address.city = "Tokyo";
System.out.println("Deep copy changed its city to Tokyo, original stays: " + original.address.city); // London! Safe!
}
}
👁 Shallow Copy vs Deep Copy Memory Trap Breakdown
-
The Cloneable Flaw
In Java, calling
super.clone()performs a shallow bitwise copy. It copies primitive fields (int,boolean), but for reference fields (Address address), it copies ONLY the 64-bit memory pointer! -
The Shallow Copy Trap
In Step 1: Both
originalandshallowhave distinct Customer objects on the Heap, BUT both point to the exact sameAddressobject in memory. Mutatingshallow.address.city = "London"quietly corrupts the original customer's data! -
The Deep Copy Solution
Line 20:
new Customer(this.name, new Address(this.address.city))creates a brand newAddressobject on the Heap with duplicated values. Modifying the copy has zero effect on the original. -
Modern Best Practice
Joshua Bloch (Effective Java) explicitly advises: Avoid
Cloneable! Prefer Copy Constructors (public Customer(Customer other)) or factory methods instead.