[go: up one dir, main page]

0% found this document useful (0 votes)
7 views49 pages

Oops

The document provides a comprehensive overview of Object-Oriented Programming (OOP), detailing its fundamental concepts, such as classes, objects, and the four pillars of OOP: encapsulation, abstraction, inheritance, and polymorphism. It includes examples in C++ and Java, along with discussions on advanced features, design principles, and best practices. The content is structured into sections that cover core language features, memory management, and design patterns relevant to OOP.

Uploaded by

Anmol Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views49 pages

Oops

The document provides a comprehensive overview of Object-Oriented Programming (OOP), detailing its fundamental concepts, such as classes, objects, and the four pillars of OOP: encapsulation, abstraction, inheritance, and polymorphism. It includes examples in C++ and Java, along with discussions on advanced features, design principles, and best practices. The content is structured into sections that cover core language features, memory management, and design patterns relevant to OOP.

Uploaded by

Anmol Sharma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

Contents

1 What Is Object-Oriented Programming? 3

2 Classes and Objects 3


2.1 Defining a Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Creating Instances (Objects) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.1 C++ Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2.2 Java Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

3 The Four Pillars of OOP 5


3.1 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.1.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.1.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3.1 C++ Example (Single Inheritance) . . . . . . . . . . . . . . . . . . . . . . . . 9
3.3.2 Java Example (Single Inheritance) . . . . . . . . . . . . . . . . . . . . . . . . 9
3.4 Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.4.1 C++ Runtime Polymorphism Example . . . . . . . . . . . . . . . . . . . . . . 10
3.4.2 Java Runtime Polymorphism Example . . . . . . . . . . . . . . . . . . . . . . 11

4 Core Language Features 11


4.1 Constructors and Destructors (C++ / Java) . . . . . . . . . . . . . . . . . . . . . . . 11
4.1.1 C++ Constructors & Destructors . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.1.2 Java Constructors & Finalizers . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.2 Access Specifiers (Public, Private, Protected) . . . . . . . . . . . . . . . . . . . . . . 13
4.2.1 C++ Access Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.2.2 Java Access Modifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.3 The this Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.3.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.4 Static Members and Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.4.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.4.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.5 Const Correctness (C++) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.6 final and sealed (Java) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

5 Function Overloading vs. Overriding 18


5.1 Compile-time (Static) Polymorphism: Overloading . . . . . . . . . . . . . . . . . . . 18
5.1.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.1.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
5.2 Runtime (Dynamic) Polymorphism: Overriding & Virtual Functions . . . . . . . . . 19
5.2.1 C++ Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5.2.2 Java Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

1
6 Abstract Classes and Interfaces 20
6.1 Abstract Classes (C++ / Java) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
6.2 Interfaces (Java) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

7 Inheritance in Depth 23
7.1 Single Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
7.2 Multiple Inheritance & the “Diamond Problem” (C++) . . . . . . . . . . . . . . . . . 23
7.3 Interface Inheritance (Java) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
7.4 Aggregation vs. Composition vs. Association . . . . . . . . . . . . . . . . . . . . . . . 25

8 Memory Layout & Object Lifecycle 27


8.1 Stack vs. Heap Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
8.1.1 C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
8.1.2 Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
8.2 Object Construction & Destruction Order . . . . . . . . . . . . . . . . . . . . . . . . 27
8.2.1 C++ (Multiple Inheritance & Member Initialization) . . . . . . . . . . . . . . 27
8.2.2 Java (Initialization Order) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
8.3 Copy Constructor, Assignment Operator, and the Rule of Three/Five (C++) . . . . 29

9 Advanced C++ Features (OOP-Related) 30


9.1 Virtual Tables (vtables) & Dynamic Dispatch . . . . . . . . . . . . . . . . . . . . . . 31
9.2 Pure Virtual Functions and Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 31
9.3 Smart Pointers & RAII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
9.4 Templates vs. Runtime Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . 32

10 Advanced Java Features (OOP-Related) 33


10.1 Garbage Collection & Finalizers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
10.2 Reflection & Dynamic Proxies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
10.3 Default and Static Methods on Interfaces . . . . . . . . . . . . . . . . . . . . . . . . 35

11 Design Principles & Best Practices 35


11.1 SOLID Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
11.2 DRY, KISS, YAGNI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
11.3 Cohesion & Coupling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
11.4 Design Patterns (Overview) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

12 Common OOP Pitfalls & How to Avoid Them 39

13 OOP in Practice: Sample Mini-Project 40


13.1 Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
13.2 Java Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
13.2.1 Enums for Priority and Status . . . . . . . . . . . . . . . . . . . . . . . . . . 41
13.2.2 User Class (Encapsulation) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
13.2.3 Task Class (Encapsulation, Association) . . . . . . . . . . . . . . . . . . . . . 42
13.2.4 TaskRepository Interface (Abstraction, DIP) . . . . . . . . . . . . . . . . . . 44
13.2.5 InMemoryTaskRepository (Concrete Implementation, Threadsafety) . . . . . 44
13.2.6 TaskService (Business Logic, SRP, OCP) . . . . . . . . . . . . . . . . . . . . . 45
13.2.7 Usage Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

2
14 Summary & Key Takeaways 48

1 What Is Object-Oriented Programming?


Object-Oriented Programming (OOP) is a programming paradigm built around the concept of
“objects,” which encapsulate data (attributes) and behavior (methods/functions). Instead of writing
programs as a collection of functions and global data, OOP organizes software around entities that
model real-world or conceptual “things.”

• Why OOP?

– Modularity & Maintainability: Code is structured as discrete pieces (classes) that can be
understood, tested, and maintained separately.
– Reusability: Through inheritance and composition, you can reuse existing classes rather than
rewriting common functionality.
– Abstraction: OOP lets you hide implementation details and expose only what’s necessary.
– Flexibility & Extensibility: Polymorphism enables swapping out implementations at run-
time or compile-time without changing calling code.

Originally popularized by languages like Smalltalk in the 1970s and then C++ in the 1980s, OOP
has since become a dominant paradigm in mainstream languages—Java, C#, Python, Ruby, and
others.

2 Classes and Objects


At the heart of OOP are classes (blueprints or templates) and objects (instances of classes). Through
classes, you define the structure (attributes/fields) and behavior (methods/functions) that all objects
of that type share.

2.1 Defining a Class


A class bundles together data members (fields) and member functions (methods). Below are mini-
mal examples in C++ and Java.

2.1.1 C++ Example

1 // File : Person . h
2 # ifndef PERSON_H
3 # define PERSON_H
4
5 # include < string >
6 # include < iostream >
7
8 class Person {
9 private :
10 std :: string name ;
11 int age ;
12

3
13 public :
14 // Constructor
15 Person ( const std :: string & name , int age )
16 : name ( name ) , age ( age ) {}
17
18 // Accessors ( getters )
19 std :: string getName () const { return name ; }
20 int getAge () const { return age ; }
21
22 // Mutators ( setters )
23 void setName ( const std :: string & newName ) { name = newName ; }
24 void setAge ( int newAge ) { age = newAge ; }
25
26 // Behavior / Method
27 void introduce () const {
28 std :: cout << " Hi , I ’m " << name << " , and I ’m "
29 << age << " years old .\ n " ;
30 }
31 };
32
33 # endif // PERSON_H

2.1.2 Java Example

1 /* File : Person . java */


2 public class Person {
3 private String name ;
4 private int age ;
5
6 // Constructor
7 public Person ( String name , int age ) {
8 this . name = name ;
9 this . age = age ;
10 }
11
12 // Getter
13 public String getName () {
14 return name ;
15 }
16
17 public int getAge () {
18 return age ;
19 }
20

21 // Setter
22 public void setName ( String name ) {
23 this . name = name ;
24 }
25

26 public void setAge ( int age ) {


27 this . age = age ;
28 }
29

4
30 // Behavior / Method
31 public void introduce () {
32 System . out . println ( " Hi , I ’m " + name
33 + " , and I ’m " + age + " years old . " ) ;
34 }
35 }

2.2 Creating Instances (Objects)


Once a class is defined, you can instantiate objects from it. Each object has its own copy of
non-static fields, and all objects of the same class share the same method implementations.

2.2.1 C++ Usage

1 # include " Person . h "


2
3 int main () {
4 Person alice ( " Alice " , 30) ;
5 alice . introduce () ; // Output : Hi , I ’m Alice , and I ’m 30 years old .
6
7 Person bob ( " Bob " , 25) ;
8 bob . introduce () ; // Output : Hi , I ’m Bob , and I ’m 25 years old .
9

10 bob . setAge (26) ;


11 bob . introduce () ; // Output : Hi , I ’m Bob , and I ’m 26 years old .
12
13 return 0;
14 }

2.2.2 Java Usage

1 public class Main {


2 public static void main ( String [] args ) {
3 Person alice = new Person ( " Alice " , 30) ;
4 alice . introduce () ; // Output : Hi , I ’m Alice , and I ’m 30 years old
.
5
6 Person bob = new Person ( " Bob " , 25) ;
7 bob . introduce () ; // Output : Hi , I ’m Bob , and I ’m 25 years old .
8
9 bob . setAge (26) ;
10 bob . introduce () ; // Output : Hi , I ’m Bob , and I ’m 26 years old .
11 }
12 }

3 The Four Pillars of OOP


Most interviewers expect you to be able to define, illustrate, and reason about the four fundamental
principles (or “pillars”) of OOP:

5
1. Encapsulation

2. Abstraction

3. Inheritance

4. Polymorphism

3.1 Encapsulation
Definition Encapsulation is the bundling of data (attributes) and code (methods) together such
that the internal representation of an object is hidden from the outside. Access to data is restricted
through well-defined interfaces (e.g., getters/setters or public methods), preventing external code
from putting the object into an invalid or inconsistent state.

Why It Matters

– Data Hiding / Information Hiding: Clients of a class don’t need to know—and shouldn’t know—the
details of how the class stores or manages its state.

– Maintainability: If you decide to change internal data representation (e.g., switch from std::vector
to std::list), external code need not change as long as the public interface remains stable.

– Safety: Prevents external code from directly manipulating fields in invalid ways (e.g., setting age
to a negative number).

3.1.1 C++ Example

1 class BankAccount {
2 private :
3 int accountNumber ;
4 double balance ;
5
6 public :
7 BankAccount ( int acctNum , double initialBalance )
8 : accountNumber ( acctNum ) , balance ( initialBalance ) {
9 if ( initialBalance < 0) {
10 throw std :: invalid_argument (
11 " Initial balance cannot be negative " ) ;
12 }
13 }
14
15 double getBalance () const {
16 return balance ;
17 }
18
19 void deposit ( double amount ) {
20 if ( amount <= 0) {
21 throw std :: invalid_argument (
22 " Deposit amount must be positive " ) ;
23 }
24 balance += amount ;
25 }

6
26
27 void withdraw ( double amount ) {
28 if ( amount <= 0) {
29 throw std :: invalid_argument (
30 " Withdrawal amount must be positive " ) ;
31 }
32 if ( amount > balance ) {
33 throw std :: runtime_error ( " Insufficient funds " ) ;
34 }
35 balance -= amount ;
36 }
37 };

3.1.2 Java Example

1 public class BankAccount {


2 private int accountNumber ;
3 private double balance ;
4

5 public BankAccount ( int acctNum , double initialBalance ) {


6 if ( initialBalance < 0) {
7 throw new I l l e g a l A r g u m e n t E x c e p t i o n (
8 " Initial balance cannot be negative " ) ;
9 }
10 this . accountNumber = acctNum ;
11 this . balance = initialBalance ;
12 }
13
14 public double getBalance () {
15 return balance ;
16 }
17
18 public void deposit ( double amount ) {
19 if ( amount <= 0) {
20 throw new I l l e g a l A r g u m e n t E x c e p t i o n (
21 " Deposit amount must be positive " ) ;
22 }
23 balance += amount ;
24 }
25
26 public void withdraw ( double amount ) {
27 if ( amount <= 0) {
28 throw new I l l e g a l A r g u m e n t E x c e p t i o n (
29 " Withdrawal amount must be positive " ) ;
30 }
31 if ( amount > balance ) {
32 throw new I l l e g a l S t a t e E x c e p t i o n ( " Insufficient funds " ) ;
33 }
34 balance -= amount ;
35 }
36 }

7
3.2 Abstraction
Definition Abstraction means exposing only the relevant functionality of an object and hiding the
unnecessary details. While encapsulation is more about “hiding data,” abstraction is about “hiding
complexity.” You present a simplified model to the user of a class, exposing only what’s necessary
to accomplish a task.

Why It Matters

– Reduced Complexity: Users of a class need only understand its interface, not the intricate internal
workings.

– Focus on What, Not How: Clients can call sort() on an array without needing to know whether
QuickSort, MergeSort, or TimSort is used underneath.

– Layered Architecture: High-level modules don’t need to know about low-level implementation
details, enabling separation of concerns.

Example: Stream Abstraction in Java In Java’s I/O library, InputStream is an abstract


class:
1 public abstract class InputStream {
2 public abstract int read () throws IOException ;
3
4 public int read ( byte [] b ) throws IOException {
5 // calls read () repeatedly
6 }
7 // ...
8 }

Concrete subclasses (FileInputStream, ByteArrayInputStream, BufferedInputStream, etc.)


implement read(). A method that requires reading from “some input” can accept an InputStream
and remain oblivious to the actual data source:
1 public void copyData ( InputStream in , OutputStream out )
2 throws IOException {
3 byte [] buffer = new byte [4096];
4 int bytesRead ;
5 while (( bytesRead = in . read ( buffer ) ) != -1) {
6 out . write ( buffer , 0 , bytesRead ) ;
7 }
8 }

The copyData method works with any InputStream—the client doesn’t care if it’s reading from
a file, network socket, or memory buffer.

3.3 Inheritance
Definition Inheritance allows one class (subclass or derived class) to acquire the properties (fields,
methods) of another class (superclass or base class). It models an “is-a” relationship: if Dog inherits
from Animal, then a Dog is an Animal.

8
Why It Matters

– Code Reuse: You can reuse fields and methods of the base class without rewriting them in the
derived class.

– Polymorphic Substitutability: You can treat a derived class object as if it were an instance of the
base class.

– Logical Hierarchies: Natural modeling of real-world relationships (e.g., Employee → Manager).

3.3.1 C++ Example (Single Inheritance)

1 class Animal {
2 public :
3 void eat () { std :: cout << " Animal eats .\ n " ; }
4 void sleep () { std :: cout << " Animal sleeps .\ n " ; }
5
6 // Virtual method for polymorphism
7 virtual void makeSound () {
8 std :: cout << " Animal makes a sound .\ n " ;
9 }
10

11 virtual ~ Animal () = default ; // Virtual destructor


12 };
13
14 class Dog : public Animal {
15 public :
16 void makeSound () override {
17 std :: cout << " Woof !\ n " ;
18 }
19
20 void fetch () {
21 std :: cout << " Dog fetches the ball .\ n " ;
22 }
23 };
24
25 int main () {
26 Animal * a = new Dog () ; // Upcast Dog * Animal *
27 a - > makeSound () ; // Calls Dog :: makeSound () prints " Woof !"
28 a - > eat () ; // Inherited from Animal
29 delete a ;
30 return 0;
31 }

3.3.2 Java Example (Single Inheritance)

1 class Animal {
2 public void eat () {
3 System . out . println ( " Animal eats . " ) ;
4 }
5 public void sleep () {
6 System . out . println ( " Animal sleeps . " ) ;

9
7 }
8 public void makeSound () {
9 System . out . println ( " Animal makes a sound . " ) ;
10 }
11 }
12
13 class Dog extends Animal {
14 @Override
15 public void makeSound () {
16 System . out . println ( " Woof ! " ) ;
17 }
18 public void fetch () {
19 System . out . println ( " Dog fetches the ball . " ) ;
20 }
21 }
22

23 public class Main {


24 public static void main ( String [] args ) {
25 Animal a = new Dog () ; // Upcast Dog Animal
26 a . makeSound () ; // Woof !
27 a . eat () ; // Animal eats .
28 // a . fetch () ; // C o m p i l e time error : fetch () not in Animals
interface
29 }
30 }

3.4 Polymorphism
Definition Polymorphism (from Greek “many forms”) enables objects of different types to be
treated as objects of a common supertype. There are two main categories:
a. Compile-time (Static) Polymorphism: Achieved via method (or operator) overloading.
b. Runtime (Dynamic) Polymorphism: Achieved via method overriding (with virtual func-
tions in C++, or simply overridden methods in Java) and dynamic dispatch.
Polymorphism allows writing flexible, extensible code. For example, a function that takes
an Animal reference can work with Dog, Cat, or any future subclass of Animal that overrides
makeSound().

3.4.1 C++ Runtime Polymorphism Example

1 void letAnimalSpeak ( Animal & a ) {


2 a . makeSound () ; // Calls the appropriate override
3 }
4
5 int main () {
6 Dog dog ;
7 Animal animal ;
8 letAnimalSpeak ( dog ) ; // Woof !
9 letAnimalSpeak ( animal ) ; // Animal makes a sound .
10 return 0;
11 }

10
3.4.2 Java Runtime Polymorphism Example

1 public void letAnimalSpeak ( Animal a ) {


2 a . makeSound () ;
3 }
4
5 public static void main ( String [] args ) {
6 Dog dog = new Dog () ;
7 Animal animal = new Animal () ;
8 letAnimalSpeak ( dog ) ; // Woof !
9 letAnimalSpeak ( animal ) ; // Animal makes a sound .
10 }

4 Core Language Features


Beyond the four pillars, there are many language-level constructs that support or extend OOP. In
this section, we’ll cover the major ones.

4.1 Constructors and Destructors (C++ / Java)


4.1.1 C++ Constructors & Destructors
– Constructor: A special method invoked when an object is created; used to initialize member
variables.

– Destructor: A special method called when an object goes out of scope (or is deleted); used to
release resources.

Example:
1 class MyClass {
2 private :
3 int * data ;
4 public :
5 // Default constructor
6 MyClass () : data ( new int [100]) {
7 std :: cout << " Constructor : Allocated array of 100 ints .\ n " ;
8 }
9
10 // Parameterized constructor
11 MyClass ( int size ) : data ( new int [ size ]) {
12 std :: cout << " Constructor : Allocated array of "
13 << size << " ints .\ n " ;
14 }
15
16 // Copy constructor ( Rule of Three !)
17 MyClass ( const MyClass & other ) {
18 // Deep copy
19 data = new int [100];
20 std :: copy ( other . data , other . data + 100 , data ) ;
21 std :: cout << " Copy constructor : Deep copy performed .\ n " ;
22 }
23

11
24 // Assignment operator ( Rule of Three )
25 MyClass & operator =( const MyClass & other ) {
26 if ( this != & other ) {
27 delete [] data ;
28 data = new int [100];
29 std :: copy ( other . data , other . data + 100 , data ) ;
30 std :: cout << " Assignment operator : Deep copy performed .\ n " ;
31 }
32 return * this ;
33 }
34
35 // Destructor
36 ~ MyClass () {
37 delete [] data ;
38 std :: cout << " Destructor : Freed allocated memory .\ n " ;
39 }
40 };

Key points:

1. Default Constructor: If you do not define any constructor, C++ provides a default (no-
argument) constructor that does nothing. Once you define any constructor, the default is not
auto-generated.

2. Copy Constructor & Assignment Operator: If your class manages resources (heap memory,
file handles, network sockets), you usually need to implement the copy constructor and assign-
ment operator to avoid shallow copies. This is often referred to as the “Rule of Three” (later
“Rule of Five” with move semantics).

3. Destructor: Responsible for cleaning up any acquired resources. Always declare the destructor
virtual if your class is intended to be used polymorphically (so derived-class destructors are
called properly).

4.1.2 Java Constructors & Finalizers


– Java has constructors but does not have destructors in the C++ sense. Instead, Java relies on
a garbage collector to reclaim memory.

– finalize() method (deprecated since Java 9 and removed in later versions) was historically
invoked by the GC before reclaiming an object, but you should avoid using it. Instead, use
try-with-resources or close() patterns (e.g., implement AutoCloseable).

Example:
1 public class ResourceHolder implements AutoCloseable {
2 private SomeResource res ;
3
4 public ResourceHolder ( String filename ) throws IOException {
5 res = new SomeResource ( filename ) ;
6 System . out . println ( " Acquired resource . " ) ;
7 }
8
9 @Override
10 public void close () {

12
11 if ( res != null ) {
12 res . release () ;
13 System . out . println ( " Released resource . " ) ;
14 }
15 }
16
17 @Deprecated
18 @Override
19 protected void finalize () throws Throwable {
20 try {
21 if ( res != null ) {
22 res . release () ;
23 System . out . println (
24 " Released resource in finalize () " ) ;
25 }
26 } finally {
27 super . finalize () ;
28 }
29 }
30 }

Usage:
1 public static void main ( String [] args ) {
2 try ( ResourceHolder rh = new ResourceHolder ( " data . txt " ) ) {
3 // Use rh ; resource a u t o closed at end of try block
4 } catch ( IOException e ) {
5 e . printStackTrace () ;
6 }
7 }

4.2 Access Specifiers (Public, Private, Protected)


4.2.1 C++ Access Specifiers
– private: Members accessible only within the class itself (and friends).

– protected: Members accessible in the class and in derived classes (but not from unrelated code).

– public: Members accessible from anywhere.

1 class Base {
2 public :
3 int x ; // Accessible from anywhere
4 protected :
5 int y ; // Accessible in Base and in any subclass
6 private :
7 int z ; // Accessible only within Base
8 };
9

10 class Derived : public Base {


11 public :
12 void f () {

13
13 x = 1; // OK ( public )
14 y = 2; // OK ( protected )
15 // z = 3; // ERROR ( private to Base )
16 }
17 };

4.2.2 Java Access Modifiers


– public: Accessible from anywhere.

– protected: Accessible within the same package, and in subclasses (even if in a different package).

– default (package-private) (no modifier): Accessible only within the same package.

– private: Accessible only within the same class.

1 package com . example ;


2
3 public class Base {
4 public int x ;
5 protected int y ;
6 int packageValue ; // package - private
7 private int z ;
8
9 public void foo () {
10 x = 1; // OK
11 y = 2; // OK
12 packageValue = 3; // OK
13 z = 4; // OK
14 }
15 }
16
17 package com . other ;
18
19 import com . example . Base ;
20
21 public class Derived extends Base {
22 public void bar () {
23 x = 5; // OK ( public )
24 y = 6; // OK ( protected , subclass )
25 // packageValue = 7; // ERROR ( different package , not visible )
26 // z = 8; // ERROR ( private )
27 }
28 }

4.3 The this Reference


– In both C++ and Java, inside an instance method, this (or *this in C++) refers to the current
object.

– Useful for disambiguating instance variables from parameters when names collide, or for passing
the current object’s reference to another method, etc.

14
4.3.1 C++ Example

1 class Rectangle {
2 private :
3 int width , height ;
4
5 public :
6 Rectangle ( int width , int height ) {
7 // Parameter names are same as member names
8 this - > width = width ;
9 this - > height = height ;
10 }
11
12 bool isSquare () const {
13 return this - > width == this - > height ;
14 }
15 };

4.3.2 Java Example

1 public class Rectangle {


2 private int width , height ;
3
4 public Rectangle ( int width , int height ) {
5 // Parameter names collide with field names :
6 this . width = width ;
7 this . height = height ;
8 }
9
10 public boolean isSquare () {
11 return this . width == this . height ;
12 }
13 }

4.4 Static Members and Methods


– Static fields and static methods belong to the class itself rather than to any particular instance.

– You can access them without creating an object.

– Often used for utility functions, constants, or data shared across all instances.

4.4.1 C++ Example

1 class MathUtils {
2 public :
3 static const double PI ; // Declaration
4 static int add ( int a , int b ) {
5 return a + b ;
6 }
7 };

15
8
9 // Definition of static const double outside the class
10 const double MathUtils :: PI = 3 .14 15 92 65 358 97 93 ;
11

12 int main () {
13 double area = MathUtils :: PI * 2 * 2;
14 int sum = MathUtils :: add (3 , 4) ; // 7
15 return 0;
16 }

4.4.2 Java Example

1 public class MathUtils {


2 public static final double PI = 3 .1 41 592 65 35 897 93 ;
3 public static int add ( int a , int b ) {
4 return a + b ;
5 }
6 }
7
8 public class Main {
9 public static void main ( String [] args ) {
10 double area = MathUtils . PI * 2 * 2;
11 int sum = MathUtils . add (3 , 4) ; // 7
12 }
13 }

4.5 Const Correctness (C++)


– C++ allows you to mark methods as const, promising not to modify the object’s state.

– You can also have const member variables (initialized only via constructor’s initializer list).

– Helps catch unintended modifications at compile time.

1 class Person {
2 private :
3 std :: string name ;
4 int age ;
5
6 public :
7 Person ( const std :: string & name , int age )
8 : name ( name ) , age ( age ) {}
9

10 std :: string getName () const {


11 // This method promises not to modify any member variable .
12 return name ;
13 }
14

15 void setName ( const std :: string & newName ) {


16 name = newName ;
17 }
18

16
19 int getAge () const {
20 return age ;
21 }
22

23 void celebr ateBirth day () {


24 age ++;
25 }
26 };
27
28 void printName ( const Person & p ) {
29 std :: cout << p . getName () << " \ n " ; // OK , getName () is const
30 // p . celebr ateBirth day () ; // Error : cel ebrateBir thday () is non - const
31 }

4.6 final and sealed (Java)


– Java’s final keyword can be used to mark a class as non-extendable or a method as non-
overridable.
– From Java 17 onward, you can also use the sealed keyword to explicitly specify which classes
are allowed to extend it.

1 // final class : cannot be subclassed


2 public final class MathUtils {
3 public static final double PI = 3 .1 41 592 65 35 897 93 ;
4

5 public static int add ( int a , int b ) {


6 return a + b ;
7 }
8 }
9
10 // sealed class : restrict inheritance
11 public sealed class Shape permits Circle , Rectangle {
12 public abstract double area () ;
13 }
14
15 public final class Circle extends Shape {
16 private double radius ;
17 public Circle ( double r ) { this . radius = r ; }
18 @Override
19 public double area () {
20 return Math . PI * radius * radius ;
21 }
22 }
23
24 public final class Rectangle extends Shape {
25 private double width , height ;
26 public Rectangle ( double w , double h ) {
27 this . width = w ;
28 this . height = h ;
29 }
30 @Override
31 public double area () {

17
32 return width * height ;
33 }
34 }

5 Function Overloading vs. Overriding


Understanding the difference between overloading (a form of compile-time polymorphism) and over-
riding (runtime polymorphism) is crucial.

5.1 Compile-time (Static) Polymorphism: Overloading


– Overloading: Multiple functions/methods share the same name but differ in parameter lists
(number, order, or types). The compiler chooses which overload to call based on the compile-
time types of arguments.

5.1.1 C++ Example

1 # include < iostream >


2
3 class Printer {
4 public :
5 void print ( int x ) {
6 std :: cout << " Printing int : " << x << " \ n " ;
7 }
8
9 void print ( double x ) {
10 std :: cout << " Printing double : " << x << " \ n " ;
11 }
12

13 void print ( const std :: string & s ) {


14 std :: cout << " Printing string : " << s << " \ n " ;
15 }
16 };
17
18 int main () {
19 Printer p ;
20 p . print (42) ; // Calls print ( int )
21 p . print (3.1415) ; // Calls print ( double )
22 p . print ( " Hello World " ) ; // Calls print ( string )
23 return 0;
24 }

5.1.2 Java Example

1 public class Printer {


2 public void print ( int x ) {
3 System . out . println ( " Printing int : " + x ) ;
4 }
5
6 public void print ( double x ) {

18
7 System . out . println ( " Printing double : " + x ) ;
8 }
9
10 public void print ( String s ) {
11 System . out . println ( " Printing string : " + s ) ;
12 }
13
14 public static void main ( String [] args ) {
15 Printer p = new Printer () ;
16 p . print (42) ; // print ( int )
17 p . print (3.1415) ; // print ( double )
18 p . print ( " Hello World " ) ; // print ( string )
19 }
20 }

Key Takeaway: Overloading is purely a compile-time mechanism; there is no runtime “dispatch.”


The compiler chooses which version to call by matching parameter types.

5.2 Runtime (Dynamic) Polymorphism: Overriding & Virtual Functions


– Overriding: A subclass provides its own implementation for a method that is already defined
in its superclass. At runtime, the appropriate version is chosen based on the actual object’s
type—this is called dynamic dispatch. In C++, you must declare the base class method as
virtual (or mark overriding methods with override) to enable dynamic dispatch. In Java, all
non-static, non-private, non-final methods are virtual by default.

5.2.1 C++ Example

1 # include < iostream >


2
3 class Base {
4 public :
5 virtual void sayHello () {
6 std :: cout << " Hello from Base !\ n " ;
7 }
8 virtual ~ Base () = default ;
9 };
10
11 class Derived : public Base {
12 public :
13 void sayHello () override {
14 std :: cout << " Hello from Derived !\ n " ;
15 }
16 };
17

18 void greet ( Base * b ) {


19 b - > sayHello () ; // At runtime , calls Derived :: sayHello () if b points to
Derived
20 }
21
22 int main () {

19
23 Base * b1 = new Base () ;
24 Base * b2 = new Derived () ;
25 greet ( b1 ) ; // Prints Hello from Base !
26 greet ( b2 ) ; // Prints Hello from Derived !
27 delete b1 ;
28 delete b2 ;
29 return 0;
30 }

5.2.2 Java Example

1 class Base {
2 public void sayHello () {
3 System . out . println ( " Hello from Base ! " ) ;
4 }
5 }
6
7 class Derived extends Base {
8 @Override
9 public void sayHello () {
10 System . out . println ( " Hello from Derived ! " ) ;
11 }
12 }
13
14 public class Main {
15 static void greet ( Base b ) {
16 b . sayHello () ; // Calls Derived ’s method if b is instance of
Derived
17 }
18
19 public static void main ( String [] args ) {
20 Base b1 = new Base () ;
21 Base b2 = new Derived () ;
22 greet ( b1 ) ; // Hello from Base !
23 greet ( b2 ) ; // Hello from Derived !
24 }
25 }

6 Abstract Classes and Interfaces


Some languages (like Java) explicitly separate abstract classes from interfaces, while others (like
C++) achieve similar effects via pure virtual functions.

6.1 Abstract Classes (C++ / Java)


C++ Abstract Class with Pure Virtual Functions A class becomes abstract if it has at
least one pure virtual function (= 0). You cannot instantiate an abstract class; you must derive
from it and implement all pure virtual functions.
1 class Shape {
2 public :

20
3 // Pure virtual function : makes Shape abstract
4 virtual double area () const = 0;
5
6 // A virtual destructor is strongly recommended
7 virtual ~ Shape () = default ;
8 };
9
10 class Circle : public Shape {
11 private :
12 double radius ;
13 public :
14 Circle ( double r ) : radius ( r ) {}
15 double area () const override {
16 return 3.1415 92653589 793 * radius * radius ;
17 }
18 };
19
20 class Rectangle : public Shape {
21 private :
22 double width , height ;
23 public :
24 Rectangle ( double w , double h ) : width ( w ) , height ( h ) {}
25 double area () const override {
26 return width * height ;
27 }
28 };
29
30 int main () {
31 // Shape s ; // Error : cannot instantiate abstract class
32 Shape * c = new Circle (2.5) ;
33 Shape * r = new Rectangle (3.0 , 4.0) ;
34 std :: cout << " Circle area : " << c - > area () << " \ n " ; // 19.634954...
35 std :: cout << " Rectangle area : " << r - > area () << " \ n " ; // 12.0
36 delete c ;
37 delete r ;
38 return 0;
39 }

Java Abstract Class


1 public abstract class Shape {
2 // Abstract method ; no implementation
3 public abstract double area () ;
4
5 public void describe () {
6 System . out . println ( " This is a shape . " ) ;
7 }
8 }
9
10 public class Circle extends Shape {
11 private double radius ;
12 public Circle ( double r ) {
13 this . radius = r ;
14 }
15 @Override

21
16 public double area () {
17 return Math . PI * radius * radius ;
18 }
19 }
20
21 public class Rectangle extends Shape {
22 private double width , height ;
23 public Rectangle ( double w , double h ) {
24 this . width = w ;
25 this . height = h ;
26 }
27 @Override
28 public double area () {
29 return width * height ;
30 }
31 }
32
33 public class Main {
34 public static void main ( String [] args ) {
35 // Shape s = new Shape () ; // Error : cannot instantiate abstract
class
36 Shape c = new Circle (2.5) ;
37 Shape r = new Rectangle (3 , 4) ;
38 System . out . println ( " Circle area : " + c . area () ) ;
39 System . out . println ( " Rectangle area : " + r . area () ) ;
40 }
41 }

6.2 Interfaces (Java)


– An interface is a contract: it declares method signatures (and since Java 8, default and static
methods).

– Classes implement interfaces by providing implementations for all abstract methods.

– Java allows multiple interfaces to be implemented by a single class (C++ uses multiple inheritance
of classes/interfaces to achieve this effect).

1 public interface Movable {


2 void move ( int dx , int dy ) ;
3 }
4
5 public interface Drawable {
6 void draw () ;
7 }
8
9 public class Player implements Movable , Drawable {
10 private int x , y ;
11 public Player ( int startX , int startY ) {
12 this . x = startX ;
13 this . y = startY ;
14 }
15

22
16 @Override
17 public void move ( int dx , int dy ) {
18 x += dx ;
19 y += dy ;
20 System . out . println ( " Player moved to ( " + x + " , " + y + " ) " ) ;
21 }
22
23 @Override
24 public void draw () {
25 System . out . println ( " Drawing player at ( " + x + " , " + y + " ) " ) ;
26 }
27
28 public static void main ( String [] args ) {
29 Player p = new Player (0 , 0) ;
30 p . move (5 , 3) ; // Player moved to (5 , 3)
31 p . draw () ; // Drawing player at (5 , 3)
32 }
33 }

7 Inheritance in Depth
Inheritance comes in several flavors and has important design trade-offs. Below, we explore single
vs. multiple inheritance, the diamond problem, and best practices around using inheritance vs.
composition.

7.1 Single Inheritance


– Single Inheritance: Each class has exactly one direct base class.

– Simplest form—supported by both C++ and Java.

– Avoids ambiguity: there is only one path up the hierarchy to find a method or field.

1 class Animal { /* ... */ };


2 class Mammal : public Animal { /* ... */ };
3 class Dog : public Mammal { /* ... */ };

1 class Animal { /* ... */ }


2 class Mammal extends Animal { /* ... */ }
3 class Dog extends Mammal { /* ... */ }

7.2 Multiple Inheritance & the “Diamond Problem” (C++)


– Multiple Inheritance: A class can inherit from more than one base class. C++ supports this
directly, but Java does not (Java uses interfaces instead).

– The classic “diamond problem” arises when two base classes share a common ancestor, and the
derived class inherits from both. Without caution, you end up with two copies of the common
ancestor’s members.

23
Diamond Example
1 class LivingBeing {
2 public :
3 void breathe () { std :: cout << " Breathing ...\ n " ; }
4 };
5
6 class Mammal : public LivingBeing {
7 public :
8 void feedMilk () { std :: cout << " Feeding milk ...\ n " ; }
9 };
10
11 class WingedAnimal : public LivingBeing {
12 public :
13 void flapWings () { std :: cout << " Flapping wings ...\ n " ; }
14 };
15
16 // Bat inherits from both Mammal and WingedAnimal
17 class Bat : public Mammal , public WingedAnimal {
18 public :
19 void echoLocate () { std :: cout << " E c h o locating ...\ n " ; }
20 };
21
22 int main () {
23 Bat b ;
24 b . breathe () ;
25 // ERROR : ambiguous : which breathe () ? from Mammal :: LivingBeing
26 // or WingedAnimal :: LivingBeing
27 return 0;
28 }

To resolve ambiguity, C++ allows virtual inheritance:


1 class LivingBeing {
2 public :
3 void breathe () { std :: cout << " Breathing ...\ n " ; }
4 };
5
6 class Mammal : virtual public LivingBeing {
7 public :
8 void feedMilk () { std :: cout << " Feeding milk ...\ n " ; }
9 };
10
11 class WingedAnimal : virtual public LivingBeing {
12 public :
13 void flapWings () { std :: cout << " Flapping wings ...\ n " ; }
14 };
15
16 class Bat : public Mammal , public WingedAnimal {
17 public :
18 void echoLocate () { std :: cout << " E c h o locating ...\ n " ; }
19 };
20
21 int main () {
22 Bat b ;
23 b . breathe () ; // OK : only one LivingBeing subobject

24
24 b . feedMilk () ;
25 b . flapWings () ;
26 b . echoLocate () ;
27 return 0;
28 }

By inheriting LivingBeing virtually in both Mammal and WingedAnimal, there is only one shared
LivingBeing subobject in Bat, so calling b.breathe() is unambiguous.

7.3 Interface Inheritance (Java)


– Java does not allow multiple inheritance of classes, but it allows a class to implement multiple
interfaces.
– Because interfaces do not carry implementation (except default methods since Java 8), there is
no diamond problem for data members—only potential conflicts for default methods, which must
be resolved explicitly.

1 interface Winged {
2 void flapWings () ;
3 }
4
5 interface Mammalian {
6 void feedMilk () ;
7 }
8

9 class Bat implements Winged , Mammalian {


10 @Override
11 public void flapWings () {
12 System . out . println ( " Flapping wings ... " ) ;
13 }
14 @Override
15 public void feedMilk () {
16 System . out . println ( " Feeding milk ... " ) ;
17 }
18 public void breathe () {
19 System . out . println ( " Breathing ... " ) ;
20 }
21 }
22
23 public class Main {
24 public static void main ( String [] args ) {
25 Bat b = new Bat () ;
26 b . breathe () ;
27 b . feedMilk () ;
28 b . flapWings () ;
29 }
30 }

7.4 Aggregation vs. Composition vs. Association


When modeling relationships between classes, it’s critical to understand the difference between
association, aggregation, and composition.

25
1. Association: A general “uses-a” relationship; one object holds a reference/pointer to another.
Neither “owns” the other.

– Example: A Driver class has an Engine reference, but the engine could exist independently
of the driver.

2. Aggregation: A specialized form of association, implying a whole-part relationship but without


strict ownership. The “part” can exist independently of the “whole.”

– Example: A Team class aggregates Player objects. Even if the Team is disbanded, Player
objects can still exist.

3. Composition: A stronger “whole-part” relationship—often called a “has-a” relationship—where


the “part” cannot exist without the “whole.” When the “whole” is destroyed, so are its “parts.”

– Example: A House class is composed of Room objects; if you delete the House, the Room objects
cease to exist.

Composition Example (C++)


1 class Room {
2 private :
3 std :: string name ;
4 public :
5 Room ( const std :: string & name ) : name ( name ) {}
6 std :: string getName () const { return name ; }
7 };
8
9 class House {
10 private :
11 std :: vector < Room > rooms ; // Rooms are part of House
12 public :
13 House ( const std :: initializer_list < std :: string >& roomNames ) {
14 for ( const auto & rn : roomNames ) {
15 rooms . emplace_back ( rn ) ;
16 }
17 }
18

19 void showRooms () const {


20 for ( const Room & r : rooms ) {
21 std :: cout << r . getName () << " \ n " ;
22 }
23 }
24 };
25
26 int main () {
27 House h ({ " Kitchen " , " Living Room " , " Bedroom " , " Bathroom " }) ;
28 h . showRooms () ;
29 // When h goes out of scope , all Room objects are destroyed
30 return 0;
31 }

26
8 Memory Layout & Object Lifecycle
Understanding how objects live in memory and how they are constructed and destroyed is especially
important in C++ (less so in Java, where GC abstracts much of this away).

8.1 Stack vs. Heap Allocation


8.1.1 C++
– Stack Allocation: Objects created with automatic storage (e.g., MyClass obj;). Constructed
when execution reaches their definition and destroyed automatically when going out of scope.

– Heap Allocation: Objects created via new (e.g., MyClass* p = new MyClass();). Must be
explicitly deallocated with delete; otherwise memory leaks occur.

1 void foo () {
2 MyClass obj1 ; // Allocated on stack ;
3 // destroyed at end of foo ()
4 MyClass * obj2 = new MyClass () ;
5 // ... use obj2 ...
6 delete obj2 ; // Must manually delete ( calls destructor )
7 }

8.1.2 Java
– In Java, all objects are allocated on the heap (technically, some JVMs may optimize small
objects to be stack-allocated, but that’s transparent to the developer).

– Primitive local variables (e.g., int x = 5;) are stored on the stack; object references (e.g., Person
p = new Person();) are on the stack, but the actual Person object is on the heap.

– Garbage Collector (GC) reclaims memory when objects become unreachable.

8.2 Object Construction & Destruction Order


8.2.1 C++ (Multiple Inheritance & Member Initialization)
1. Base class constructors run first (in the order of inheritance).

2. Member objects are constructed next, in the order they appear in the class definition (regard-
less of initializer list order).

3. Derived class constructor body executes last.

4. Destruction happens in reverse: first the derived class’s destructor body, then member fields’
destructors (in reverse order), then base class destructors (in reverse inheritance order).

1 # include < iostream >


2

3 class A {
4 public :
5 A () { std :: cout << " A :: A () \ n " ; }

27
6 ~ A () { std :: cout << " A ::~ A () \ n " ; }
7 };
8
9 class B {
10 public :
11 B () { std :: cout << " B :: B () \ n " ; }
12 ~ B () { std :: cout << " B ::~ B () \ n " ; }
13 };
14
15 class C : public A {
16 private :
17 B b_member ;
18 public :
19 C () : b_member () {
20 std :: cout << " C :: C () \ n " ;
21 }
22 ~ C () {
23 std :: cout << " C ::~ C () \ n " ;
24 }
25 };
26
27 int main () {
28 C c;
29 return 0;
30 }

Output:

A::A() // Base A constructor


B::B() // Member B constructor
C::C() // Derived C constructor body
C::~C() // Derived C destructor
B::~B() // Member B destructor
A::~A() // Base A destructor

8.2.2 Java (Initialization Order)


1. Static initializers (in textual order).

2. Instance field initializers & instance initializer blocks (in textual order).

3. Superclass constructors (recursively).

4. Subclass constructor body.

1 public class A {
2 {
3 System . out . println ( " A : Instance initializer " ) ;
4 }
5 public A () {
6 System . out . println ( " A : Constructor " ) ;
7 }

28
8 }
9
10 public class B extends A {
11 {
12 System . out . println ( " B : Instance initializer " ) ;
13 }
14 public B () {
15 System . out . println ( " B : Constructor " ) ;
16 }
17 }
18
19 public class Main {
20 public static void main ( String [] args ) {
21 B b = new B () ;
22 }
23 }

Output:

A: Instance initializer
A: Constructor
B: Instance initializer
B: Constructor

Note: Java’s GC finally calls finalize() (if implemented) unpredictably, but you should not
rely on finalizers.

8.3 Copy Constructor, Assignment Operator, and the Rule of Three/Five (C++)
When a C++ class manages dynamically allocated resources, you must carefully implement:

– Destructor: to free resources.

– Copy constructor: to make a deep copy when constructing a new object from an existing one.

– Copy assignment operator: to free existing resources and copy data from the source.

– With C++11, we also consider move constructor and move assignment operator for more
efficient transfers (“Rule of Five”).

Rule of Three Example


1 class Buffer {
2 private :
3 size_t size ;
4 char * data ;
5
6 public :
7 // Constructor
8 Buffer ( size_t sz ) : size ( sz ) , data ( new char [ sz ]) {}
9
10 // Destructor
11 ~ Buffer () {
12 delete [] data ;

29
13 }
14
15 // Copy Constructor
16 Buffer ( const Buffer & other )
17 : size ( other . size ) , data ( new char [ other . size ]) {
18 std :: copy ( other . data , other . data + size , data ) ;
19 }
20
21 // Copy Assignment
22 Buffer & operator =( const Buffer & other ) {
23 if ( this != & other ) {
24 delete [] data ; // Free old memory
25 size = other . size ;
26 data = new char [ size ];
27 std :: copy ( other . data , other . data + size , data ) ;
28 }
29 return * this ;
30 }
31
32 // ( Optional ) Move Constructor ( C ++11+)
33 Buffer ( Buffer && other ) noexcept
34 : size ( other . size ) , data ( other . data ) {
35 other . size = 0;
36 other . data = nullptr ;
37 }
38
39 // ( Optional ) Move Assignment
40 Buffer & operator =( Buffer && other ) noexcept {
41 if ( this != & other ) {
42 delete [] data ;
43 size = other . size ;
44 data = other . data ;
45 other . size = 0;
46 other . data = nullptr ;
47 }
48 return * this ;
49 }
50 };

Without implementing all three (or five), you risk:

– Double-free errors (two objects referencing the same raw pointer).

– Memory leaks (shallow copy leaves original pointer dangling).

9 Advanced C++ Features (OOP-Related)


To truly master OOP questions in a C++ context, you should understand how C++ implements
polymorphism under the hood (vtables), when to use pure virtual functions vs. templates, and
idioms like RAII and smart pointers.

30
9.1 Virtual Tables (vtables) & Dynamic Dispatch
– When a class has one or more virtual methods, the compiler typically creates a virtual table
(vtable) for that class.

– Each object of such a class stores a hidden pointer (vptr) to its class’s vtable.

– The vtable is essentially an array of function pointers, one for each virtual method. When you
call a virtual method on an object, the program looks up the function pointer in the vtable and
jumps to it—achieving runtime polymorphism.

Simplified memory layout:


Object instance in memory:
[ vptr → (class’s vtable) ]
[ non-virtual data members... ]

Class’s vtable (hidden):


[ &Base::virtualMethod1, &Base::virtualMethod2, ... ]
When a derived class overrides a virtual method, the derived class’s vtable entry for that
method points to the derived version. This is why Base* b = new Derived(); b->foo(); calls
Derived::foo().

9.2 Pure Virtual Functions and Interfaces


– As shown earlier, a class with one or more pure virtual functions is abstract and cannot be
instantiated.

– In C++, “interfaces” (in the Java sense) are typically modeled as classes with only pure virtual
methods (and no data members).

1 class Drawable {
2 public :
3 virtual void draw () const = 0;
4 virtual ~ Drawable () = default ;
5 };
6
7 class Circle : public Drawable {
8 public :
9 void draw () const override {
10 // draw a circle
11 }
12 };

9.3 Smart Pointers & RAII


– RAII (Resource Acquisition Is Initialization): An idiom where resource acquisition happens
in a constructor, and resource release happens in a destructor. Ensures exception safety.

– Smart Pointers (std::unique_ptr, std::shared_ptr, std::weak_ptr) manage dynamic mem-


ory automatically, freeing you from manually calling delete.

31
1 # include < memory >
2 # include < iostream >
3
4 class Widget {
5 public :
6 Widget () { std :: cout << " Widget acquired .\ n " ; }
7 ~ Widget () { std :: cout << " Widget destroyed .\ n " ; }
8 void doWork () { std :: cout << " Widget working ...\ n " ; }
9 };
10
11 int main () {
12 {
13 std :: unique_ptr < Widget > w1 = std :: make_unique < Widget >() ;
14 w1 - > doWork () ;
15 // When w1 goes out of scope , ~ Widget () is called automatically
16 }
17
18 {
19 std :: shared_ptr < Widget > w2 = std :: make_shared < Widget >() ;
20 {
21 std :: shared_ptr < Widget > w3 = w2 ;
22 w3 - > doWork () ;
23 // w2 and w3 share ownership ; destructed only when both go out
of scope
24 }
25 std :: cout << " w3 out of scope , but widget still alive via w2 \ n " ;
26 }
27 std :: cout << " w2 out of scope , Widget destroyed now \ n " ;
28 return 0;
29 }

Key Takeaways:

– Prefer std::unique_ptr for sole ownership (lowest overhead).

– Use std::shared_ptr when multiple owners are needed, but be mindful of overhead and potential
cycles (use std::weak_ptr to break cycles).

– Rely on RAII: wrap resources (file handles, mutexes, sockets) in classes whose destructors release
them.

9.4 Templates vs. Runtime Polymorphism


– Templates (compile-time polymorphism): Generate code for each template instantiation
(e.g., vector<int>, vector<string>), enabling type safety and zero-cost abstraction.

– Runtime Polymorphism: Virtual dispatch has some overhead (indirection through vtable).
Templates are resolved at compile time, so there is no runtime dispatch cost.

Example: A generic function using templates


1 template < typename T >
2 T maxValue ( T a , T b ) {
3 return ( a > b ) ? a : b ;

32
4 }
5
6 int main () {
7 int x = maxValue (3 , 7) ; // instantiate maxValue < int >
8 double y = maxValue (3.14 , 2.71) ; // instantiate maxValue < double >
9 }

10 Advanced Java Features (OOP-Related)


While Java abstracts away memory management, it has its own set of advanced OOP-centric fea-
tures: garbage collection nuances, reflection, dynamic proxies, and enriched interface semantics.

10.1 Garbage Collection & Finalizers


– Java’s Garbage Collector (GC) automatically reclaims memory of objects that are no longer
reachable.

– You cannot predict exactly when GC will run, so you should not rely on finalizers (finalize()),
as they are deprecated and have unpredictable timing.

– Instead, use try-with-resources and AutoCloseable to deterministically release non-memory


resources (files, streams).

1 public class Resource implements AutoCloseable {


2 public Resource () {
3 System . out . println ( " Acquired resource . " ) ;
4 }
5
6 public void doSomething () {
7 System . out . println ( " Using resource . " ) ;
8 }
9
10 @Override
11 public void close () {
12 System . out . println ( " Released resource . " ) ;
13 }
14 }
15
16 // Usage
17 public static void main ( String [] args ) {
18 try ( Resource r = new Resource () ) {
19 r . doSomething () ;
20 } // r . close () automatically called here
21 }

10.2 Reflection & Dynamic Proxies


Reflection: Allows inspection of classes, methods, fields at runtime, circumventing compile-time
type safety. Useful for frameworks (e.g., JUnit, Spring) that discover annotations or dynamic
behavior.

33
1 Class <? > cls = Class . forName ( " com . example . MyClass " ) ;
2 Method m = cls . getMethod ( " myMethod " , String . class , int . class ) ;
3 Object instance = cls . getConstructor () . newInstance () ;
4 Object result = m . invoke ( instance , " hello " , 42) ;

Dynamic Proxies: Java can generate proxy objects at runtime that implement a set of inter-
faces, delegating method calls to an InvocationHandler. Widely used in AOP (Aspect-Oriented
Programming) and RPC frameworks.
1 import java . lang . reflect .*;
2
3 interface Service {
4 void serve () ;
5 }
6
7 class RealService implements Service {
8 @Override
9 public void serve () {
10 System . out . println ( " Serving ... " ) ;
11 }
12 }
13
14 class LoggingHandler implements Invoca tionHand ler {
15 private final Object target ;
16 public LoggingHandler ( Object target ) {
17 this . target = target ;
18 }
19 @Override
20 public Object invoke ( Object proxy , Method method ,
21 Object [] args ) throws Throwable {
22 System . out . println ( " Before method : " + method . getName () ) ;
23 Object result = method . invoke ( target , args ) ;
24 System . out . println ( " After method : " + method . getName () ) ;
25 return result ;
26 }
27 }
28
29 public class Main {
30 public static void main ( String [] args ) {
31 Service real = new RealService () ;
32 Service proxy = ( Service ) Proxy . newProxyInstance (
33 Service . class . getClassLoader () ,
34 new Class <? >[]{ Service . class } ,
35 new LoggingHandler ( real )
36 );
37 proxy . serve () ;
38 // Output :
39 // Before method : serve
40 // Serving ...
41 // After method : serve
42 }
43 }

34
10.3 Default and Static Methods on Interfaces
Since Java 8, interfaces can have default methods (with a method body) and static methods.
This allows interface evolution without breaking existing implementations.
1 interface Calculator {
2 int add ( int a , int b ) ;
3
4 default int subtract ( int a , int b ) {
5 return a - b ; // Default implementation
6 }
7
8 static int multiply ( int a , int b ) {
9 return a * b ;
10 }
11 }
12
13 class MyCalculator implements Calculator {
14 @Override
15 public int add ( int a , int b ) {
16 return a + b ;
17 }
18 }
19
20 public class Main {
21 public static void main ( String [] args ) {
22 Calculator calc = new MyCalculator () ;
23 System . out . println ( calc . add (3 , 4) ) ; // 7
24 System . out . println ( calc . subtract (10 , 5) ) ; // 5 ( default method )
25 System . out . println ( Calculator . multiply (2 , 8) ) ; // 16 ( static
method )
26 }
27 }

11 Design Principles & Best Practices


Knowing OOP syntax is only half the battle in an interview; interviewers often dig deeper into your
understanding of design principles and your ability to write clean, maintainable code.

11.1 SOLID Principles


SOLID is an acronym for five design principles that guide the creation of flexible, maintainable, and
scalable software:

1. Single-Responsibility Principle (SRP) A class should have exactly one reason to change.
It should do one thing well. Example: Don’t mix file I/O and business logic in the same class.
Instead, separate them into FileManager and BusinessProcessor.

2. Open/Closed Principle (OCP) Software entities (classes, modules, functions) should be open
for extension but closed for modification. Using interfaces or abstract classes, you can add new
behavior by extending existing code rather than altering it.

35
1 // Without OCP : to add a new shape , we must modify ShapeProcessor
2 class ShapeProcessor {
3 public double area ( Shape s ) {
4 if ( s instanceof Circle ) {
5 return Math . PI * (( Circle ) s ) . radius * (( Circle ) s ) . radius ;
6 } else if ( s instanceof Rectangle ) {
7 Rectangle r = ( Rectangle ) s ;
8 return r . width * r . height ;
9 }
10 // If we add a new shape , we must modify this code !
11 return 0;
12 }
13 }
14
15 // With OCP : each shape knows how to compute its own area
16 interface Shape {
17 double area () ;
18 }
19 class Circle implements Shape { /* ... */ }
20 class Rectangle implements Shape { /* ... */ }
21
22 class ShapeProcessor {
23 public double area ( Shape s ) {
24 return s . area () ; // No need to modify this when adding new
shapes
25 }
26 }

3. Liskov Substitution Principle (LSP) Objects of a superclass should be replaceable with


objects of a subclass without altering desirable program properties. In other words, a subclass
should not violate the expectations set by its base class. Example Violation: Suppose Rectangle
is a subclass of Shape with width and height. If you create a Square subclass of Rectangle that
forces width = height, then code that sets width and height independently on a Rectangle will
break when passed a Square.
1 class Rectangle {
2 protected int width , height ;
3 public void setWidth ( int w ) { width = w ; }
4 public void setHeight ( int h ) { height = h ; }
5 public int area () { return width * height ; }
6 }
7
8 class Square extends Rectangle {
9 @Override
10 public void setWidth ( int w ) {
11 width = height = w ;
12 }
13 @Override
14 public void setHeight ( int h ) {
15 width = height = h ;
16 }
17 }
18

36
19 // Client code violation :
20 void resize Rectangl eTo ( Rectangle r , int w , int h ) {
21 r . setWidth ( w ) ;
22 r . setHeight ( h ) ;
23 // Now expects area to be w * h
24 System . out . println ( r . area () ) ;
25 // But if r is Square , area is w * w or h *h , not w * h
26 }

4. Interface Segregation Principle (ISP) Clients should not be forced to depend on interfaces
they do not use. It’s better to have many smaller, specific interfaces rather than a large, “fat”
interface. Example: Instead of a single IMachine interface with print(), scan(), fax(), provide
IPrinter, IScanner, IFax separately. A Printer only implements IPrinter.

5. Dependency Inversion Principle (DIP) High-level modules should not depend on low-level
modules; both should depend on abstractions (e.g., interfaces). Abstractions should not depend
on details; details should depend on abstractions. Example: Instead of a Keyboard class directly
instantiating a USBPort, define an interface Port and let both USBPort and BluetoothPort
implement it. Then Keyboard depends on Port, not on a specific USBPort class.
1 interface Port {
2 void connect () ;
3 }
4
5 class USBPort implements Port {
6 public void connect () {
7 System . out . println ( " Connected via USB " ) ;
8 }
9 }
10
11 class BluetoothPort implements Port {
12 public void connect () {
13 System . out . println ( " Connected via Bluetooth " ) ;
14 }
15 }
16
17 class Keyboard {
18 private Port port ;
19 public Keyboard ( Port port ) {
20 this . port = port ;
21 }
22 public void plugIn () {
23 port . connect () ;
24 }
25 }
26
27 // Usage :
28 Port usb = new USBPort () ;
29 Keyboard keyboard = new Keyboard ( usb ) ;
30 keyboard . plugIn () ; // Connected via U S B

37
11.2 DRY, KISS, YAGNI
• DRY (Don’t Repeat Yourself ): Avoid duplicating code. If the same logic appears in multiple
places, factor it out into a single method/class.

• KISS (Keep It Simple, Stupid): Design systems in the simplest way possible. Don’t over-
engineer.

• YAGNI (You Aren’t Gonna Need It): Don’t add features or abstractions until they are
strictly necessary. Premature generalization often leads to unnecessary complexity.

11.3 Cohesion & Coupling


• Cohesion: A measure of how closely related the responsibilities of a single module or class are.
High cohesion is desirable—each class should have one, well-focused responsibility.

• Coupling: A measure of how dependent modules or classes are on one another. Low coupling is
desirable—modules should interact through well-defined interfaces, minimizing the ripple effect
of changes.

Techniques to reduce coupling:

– Depend on interfaces/abstractions rather than concrete classes.

– Use dependency injection (DI) frameworks.

– Favor composition (has-a) over inheritance (is-a) where appropriate.

11.4 Design Patterns (Overview)


Design patterns are common solutions to recurring design problems. Below is a brief list of classical
patterns; study them in more detail if you have time:

• Creational Patterns

– Singleton: Ensures a class has only one instance and provides a global access point.
– Factory Method: Defines an interface for creating an object but lets subclasses decide which
class to instantiate.
– Abstract Factory: Provides an interface for creating families of related or dependent objects
without specifying their concrete classes.
– Builder: Separates the construction of a complex object from its representation.
– Prototype: Create new objects by copying an existing object (prototype) rather than creating
from scratch.

• Structural Patterns

– Adapter: Converts the interface of a class into another interface clients expect.
– Decorator: Attach additional responsibilities dynamically to an object.
– Facade: Provides a unified interface to a set of interfaces in a subsystem.
– Composite: Compose objects into tree structures to represent part-whole hierarchies.

38
– Proxy: Provide a surrogate or placeholder for another object to control access to it.

• Behavioral Patterns

– Strategy: Define a family of algorithms, encapsulate each one, and make them interchangeable
at runtime.
– Observer: Define a one-to-many dependency between objects so that when one object changes
state, all its dependents are notified.
– Command: Encapsulate a request as an object, thereby allowing parameterization of clients
with queues, requests, and operations.
– Iterator: Provide a way to access elements of an aggregate object sequentially without expos-
ing its underlying representation.
– State: Allow an object to change its behavior when its internal state changes.

You don’t need to memorize all patterns; instead, understand the intent behind each and examine
a few common ones in detail (e.g., Singleton, Factory, Observer, Strategy).

12 Common OOP Pitfalls & How to Avoid Them


Many pitfalls stem from misunderstanding the subtleties of OOP. Interviewers often test your ability
to recognize and correct them.

1. Overusing Inheritance Inheritance should model an “is-a” relationship. If you find yourself us-
ing inheritance simply to reuse code, consider composition instead. Deep inheritance hierarchies
can lead to brittle code—changes in a base class can cascade unexpectedly.

2. Violating Encapsulation Exposing internal data structures (e.g., returning a reference to a


private list) breaks encapsulation. Instead, return a copy or unmodifiable view. Example pitfall
in Java:
1 public class Person {
2 private List < String > phoneNumbers ;
3 public List < String > getPhoneNumbers () {
4 return phoneNumbers ; // Caller can modify the list directly
5 }
6 }

Better approach:
1 public List < String > getPhoneNumbers () {
2 return Collections . unmodifiableList ( phoneNumbers ) ;
3 }

3. Leaking this During Construction (C++) If you register this (e.g., to an observer) in a
constructor before derived classes have been fully initialized, you can get into trouble when a
virtual call is made on a partly constructed object.

4. Forgetting to Make Destructors Virtual (C++) If you call delete on a base-class pointer
that points to a derived-class object, and you forgot to declare the base destructor as virtual,
only the base destructor will run—resulting in a resource leak (or other undefined behavior).

39
5. Unintentional Object Slicing (C++) If you assign a derived-class object to a base-class
object by value, only the base portion is copied—derived-class data is “sliced” off.
1 class Base { /* ... */ };
2 class Derived : public Base { /* ... additional data members ... */ };
3
4 Derived d ;
5 Base b = d ; // Slicing : the Derived - specific data is lost

6. Excessive Use of Static Methods/Fields Relying too heavily on static members can lead
to code that is hard to test (because static state is global) and inflexible (no easy way to swap
implementations at runtime).

7. Ignoring Thread Safety In multi-threaded programs, careless use of mutable shared objects
(especially static fields) can lead to race conditions. Use synchronization (synchronized in Java,
or std::mutex in C++) or immutable classes when possible.

8. Exposing Implementation Details in Public APIs If you return an ArrayList<String>


from a method, you commit yourself to that representation. It’s better to return the inter-
face type (List<String>) to leave open the possibility of changing to a LinkedList or another
implementation.

13 OOP in Practice: Sample Mini-Project


Below is a small, illustrative project that brings together many OOP concepts. Suppose you want
to model a simple Task Management System with tasks that can be assigned to users, have
priorities, statuses, and can be persisted (in memory only). This can be used to demonstrate
classes, inheritance, interfaces, encapsulation, and basic design principles.

13.1 Requirements
1. Task

– Has an ID, title, description, priority (LOW, MEDIUM, HIGH), status (NEW, IN_PROGRESS, DONE),
and an optional assignee (User).
– Can be marked as done or reopened.

2. User

– Has a userID, name, and email.


– Can have multiple tasks assigned.

3. TaskRepository (Interface)

– Defines methods to add, retrieve (by ID), update, delete tasks.

4. InMemoryTaskRepository (Concrete Implementation)

– Stores tasks in a Map<Integer, Task>.


– Provides thread-safe operations.

40
5. TaskService

– Business logic layer that uses TaskRepository.


– Methods: createTask, assignTask, changeTaskStatus, getTasksByUser, getTasksByPriority,
etc.

13.2 Java Implementation


13.2.1 Enums for Priority and Status

1 public enum Priority {


2 LOW ,
3 MEDIUM ,
4 HIGH
5 }
6
7 public enum Status {
8 NEW ,
9 IN_PROGRESS ,
10 DONE
11 }

13.2.2 User Class (Encapsulation)

1 public class User {


2 private final int userID ;
3 private String name ;
4 private String email ;
5
6 public User ( int userID , String name , String email ) {
7 this . userID = userID ;
8 this . name = name ;
9 this . email = email ;
10 }
11
12 public int getUserID () {
13 return userID ;
14 }
15

16 public String getName () {


17 return name ;
18 }
19
20 public void setName ( String name ) {
21 if ( name == null || name . isBlank () ) {
22 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Name cannot be empty " ) ;
23 }
24 this . name = name ;
25 }
26
27 public String getEmail () {
28 return email ;

41
29 }
30
31 public void setEmail ( String email ) {
32 // Simple email validation
33 if ( email == null || ! email . contains ( " @ " ) ) {
34 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Invalid email address " ) ;
35 }
36 this . email = email ;
37 }
38

39 @Override
40 public String toString () {
41 return " User { " +
42 " userID = " + userID +
43 " , name = ’ " + name + ’\ ’ ’ +
44 " , email = ’ " + email + ’\ ’ ’ +
45 ’} ’;
46 }
47 }

13.2.3 Task Class (Encapsulation, Association)

1 import java . time . LocalDateTime ;


2
3 public class Task {
4 private final int taskID ;
5 private String title ;
6 private String description ;
7 private Priority priority ;
8 private Status status ;
9 private User assignee ;
10 private LocalDateTime createdAt ;
11 private LocalDateTime updatedAt ;
12
13 public Task ( int taskID , String title , String description , Priority
priority ) {
14 if ( title == null || title . isBlank () ) {
15 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Title cannot be empty " ) ;
16 }
17 this . taskID = taskID ;
18 this . title = title ;
19 this . description = description ;
20 this . priority = priority ;
21 this . status = Status . NEW ;
22 this . assignee = null ;
23 this . createdAt = LocalDateTime . now () ;
24 this . updatedAt = this . createdAt ;
25 }
26

27 public int getTaskID () {


28 return taskID ;
29 }
30

42
31 public String getTitle () {
32 return title ;
33 }
34

35 public void setTitle ( String title ) {


36 if ( title == null || title . isBlank () ) {
37 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Title cannot be empty " ) ;
38 }
39 this . title = title ;
40 this . updatedAt = LocalDateTime . now () ;
41 }
42
43 public String getDescription () {
44 return description ;
45 }
46

47 public void setDescription ( String description ) {


48 this . description = description ;
49 this . updatedAt = LocalDateTime . now () ;
50 }
51
52 public Priority getPriority () {
53 return priority ;
54 }
55
56 public void setPriority ( Priority priority ) {
57 this . priority = priority ;
58 this . updatedAt = LocalDateTime . now () ;
59 }
60
61 public Status getStatus () {
62 return status ;
63 }
64

65 public void setStatus ( Status status ) {


66 this . status = status ;
67 this . updatedAt = LocalDateTime . now () ;
68 }
69
70 public User getAssignee () {
71 return assignee ;
72 }
73
74 public void assignTo ( User user ) {
75 this . assignee = user ;
76 this . updatedAt = LocalDateTime . now () ;
77 }
78
79 public void markDone () {
80 this . status = Status . DONE ;
81 this . updatedAt = LocalDateTime . now () ;
82 }
83
84 public void reopen () {

43
85 this . status = Status . IN_PROGRESS ;
86 this . updatedAt = LocalDateTime . now () ;
87 }
88

89 public LocalDateTime getCreatedAt () {


90 return createdAt ;
91 }
92
93 public LocalDateTime getUpdatedAt () {
94 return updatedAt ;
95 }
96
97 @Override
98 public String toString () {
99 return " Task { " +
100 " taskID = " + taskID +
101 " , title = ’ " + title + ’\ ’ ’ +
102 " , priority = " + priority +
103 " , status = " + status +
104 " , assignee = " + ( assignee != null ? assignee . getName () : "
Unassigned " ) +
105 " , createdAt = " + createdAt +
106 " , updatedAt = " + updatedAt +
107 ’} ’;
108 }
109 }

13.2.4 TaskRepository Interface (Abstraction, DIP)

1 import java . util . List ;


2 import java . util . Optional ;
3
4 public interface TaskRepository {
5 void addTask ( Task task ) ;
6 Optional < Task > getTaskByID ( int taskID ) ;
7 void updateTask ( Task task ) ;
8 void deleteTask ( int taskID ) ;
9 List < Task > getAllTasks () ;
10 }

13.2.5 InMemoryTaskRepository (Concrete Implementation, Threadsafety)

1 import java . util .*;


2 import java . util . concurrent . Concur rentHash Map ;
3
4 public class I n M e m o r y T a s k R e p o s i t o r y implements TaskRepository {
5 // Thread - safe map : no external synchronization needed
6 private final Map < Integer , Task > tasks = new ConcurrentHashMap < >() ;
7
8 @Override
9 public void addTask ( Task task ) {

44
10 if ( tasks . containsKey ( task . getTaskID () ) ) {
11 throw new I l l e g a l A r g u m e n t E x c e p t i o n (
12 " Task ID already exists : " + task . getTaskID () ) ;
13 }
14 tasks . put ( task . getTaskID () , task ) ;
15 }
16
17 @Override
18 public Optional < Task > getTaskByID ( int taskID ) {
19 return Optional . ofNullable ( tasks . get ( taskID ) ) ;
20 }
21
22 @Override
23 public void updateTask ( Task task ) {
24 if (! tasks . containsKey ( task . getTaskID () ) ) {
25 throw new N o S u c h E l e m e n t E x c e p t i o n (
26 " Task not found : " + task . getTaskID () ) ;
27 }
28 tasks . put ( task . getTaskID () , task ) ;
29 }
30
31 @Override
32 public void deleteTask ( int taskID ) {
33 tasks . remove ( taskID ) ;
34 }
35
36 @Override
37 public List < Task > getAllTasks () {
38 return new ArrayList < >( tasks . values () ) ;
39 }
40 }

13.2.6 TaskService (Business Logic, SRP, OCP)

1 import java . util . List ;


2 import java . util . stream . Collectors ;
3
4 public class TaskService {
5 private final TaskRepository repo ;
6
7 public TaskService ( TaskRepository repo ) {
8 this . repo = repo ;
9 }
10

11 public void createTask ( int id , String title , String description ,


12 Priority priority ) {
13 Task task = new Task ( id , title , description , priority ) ;
14 repo . addTask ( task ) ;
15 }
16
17 public void assignTask ( int taskID , User user ) {
18 Task task = repo . getTaskByID ( taskID )
19 . orElseThrow (() -> new N o S u c h E l e m e n t E x c e p t i o n (

45
20 " Task not found : " + taskID ) ) ;
21 task . assignTo ( user ) ;
22 repo . updateTask ( task ) ;
23 }
24
25 public void changePriority ( int taskID , Priority newPriority ) {
26 Task task = repo . getTaskByID ( taskID )
27 . orElseThrow (() -> new N o S u c h E l e m e n t E x c e p t i o n (
28 " Task not found : " + taskID ) ) ;
29 task . setPriority ( newPriority ) ;
30 repo . updateTask ( task ) ;
31 }
32
33 public void markDone ( int taskID ) {
34 Task task = repo . getTaskByID ( taskID )
35 . orElseThrow (() -> new N o S u c h E l e m e n t E x c e p t i o n (
36 " Task not found : " + taskID ) ) ;
37 task . markDone () ;
38 repo . updateTask ( task ) ;
39 }
40
41 public List < Task > getTasksByUser ( User user ) {
42 return repo . getAllTasks () . stream ()
43 . filter ( t -> t . getAssignee () != null
44 && t . getAssignee () . getUserID () == user . getUserID () )
45 . collect ( Collectors . toList () ) ;
46 }
47

48 public List < Task > g et Ta sk sBy Pr io rit y ( Priority priority ) {


49 return repo . getAllTasks () . stream ()
50 . filter ( t -> t . getPriority () == priority )
51 . collect ( Collectors . toList () ) ;
52 }
53

54 public List < Task > getAllTasks () {


55 return repo . getAllTasks () ;
56 }
57 }

13.2.7 Usage Example

1 public class Main {


2 public static void main ( String [] args ) {
3 // Dependency Injection : we could swap in a DB - backed repository
later
4 TaskRepository repo = new I n M e m o r y T a s k R e p o s i t o r y () ;
5 TaskService service = new TaskService ( repo ) ;
6
7 // Create some users
8 User alice = new User (1 , " Alice " , " alice@example . com " ) ;
9 User bob = new User (2 , " Bob " , " bob@example . com " ) ;
10
11 // Create tasks

46
12 service . createTask (100 , " Setup Dev Environment " ,
13 " Install IDE , SDK , etc . " , Priority . MEDIUM ) ;
14 service . createTask (101 , " Design Database Schema " ,
15 " Create ER diagram " , Priority . HIGH ) ;
16 service . createTask (102 , " Implement Login " ,
17 " Use JWT authentication " , Priority . HIGH ) ;
18
19 // Assign tasks
20 service . assignTask (100 , alice ) ;
21 service . assignTask (101 , bob ) ;
22
23 // Mark tasks done
24 service . markDone (100) ;
25
26 // List tasks by user
27 System . out . println ( " Alice ’s Tasks : " ) ;
28 for ( Task t : service . getTasksByUser ( alice ) ) {
29 System . out . println ( t ) ;
30 }
31
32 System . out . println ( " \ nHigh Priority Tasks : " ) ;
33 for ( Task t : service . g et Ta sks By Pr ior it y ( Priority . HIGH ) ) {
34 System . out . println ( t ) ;
35 }
36 }
37 }

Sample Output:
Alice’s Tasks:
Task{taskID=100, title=’Setup Dev Environment’, priority=MEDIUM,
status=DONE, assignee=Alice,
createdAt=2025-05-31T12:34:56.789,
updatedAt=2025-05-31T12:35:01.234}

High Priority Tasks:


Task{taskID=101, title=’Design Database Schema’, priority=HIGH,
status=NEW, assignee=Bob,
createdAt=2025-05-31T12:34:56.789,
updatedAt=2025-05-31T12:34:56.789}
Task{taskID=102, title=’Implement Login’, priority=HIGH,
status=NEW, assignee=Unassigned,
createdAt=2025-05-31T12:34:56.790,
updatedAt=2025-05-31T12:34:56.790}
This mini-project illustrates:
• Abstraction: Clients interact with TaskService and TaskRepository interfaces rather than
concrete implementations.
• Encapsulation: Fields in User and Task are private; external code manipulates them only
through public methods.

47
• Inheritance & Interfaces: Though we didn’t define a subclass hierarchy here, the pattern is
ready to extend—for instance, you could have PersistentTaskRepository implementing TaskRepository
that stores tasks in a database.

• SOLID Principles: SRP (each class has one responsibility), OCP (we can add new repository
implementations without modifying existing code), DIP (high-level TaskService depends on the
abstraction TaskRepository).

14 Summary & Key Takeaways


Preparing for OOP questions in a core CS interview (such as at Delhivery) means not only reciting
definitions but demonstrating deep understanding and fluency:

1. Know the Four Pillars Inside-Out

• Encapsulation: Hide your data; expose only what’s necessary.


• Abstraction: Provide a simple interface, hide complexity.
• Inheritance: Model “is-a” relationships; reuse code and behaviors.
• Polymorphism: Write flexible code—leverage compile-time (overloading/templates) and run-
time (virtual functions/overriding) dispatch.

2. Master Language-Specific Features

• In C++, understand constructors/destructors, copy/move semantics, virtual tables, and smart


pointers.
• In Java, be fluent with inheritance, interfaces (including default methods), garbage collection
best practices, and reflection (if you have time).

3. Practice Code Examples

• Write small programs that define class hierarchies, use virtual methods, show slicing issues,
demonstrate composition over inheritance, etc.
• Be able to trace through object lifetimes (especially in C++), including order of construc-
tion/destruction.

4. Understand Memory and Object Lifecycle

• Differentiate between stack vs. heap allocation in C++.


• Know how Java’s GC works in broad strokes (and why you don’t use destructors/finalizers the
way you would in C++).

5. Design Principles Matter

• You may be asked design-oriented questions (e.g., how you would refactor a monolithic class,
or how to design a plugin architecture). Use SOLID principles, DRY/KISS/YAGNI, and show
awareness of cohesion and coupling.
• Be ready to discuss trade-offs between inheritance and composition, or static vs. dynamic
polymorphism (templates vs. virtual methods).

6. Be Aware of Common Pitfalls

48
• For C++: “Rule of Three/Five,” forgetting to mark destructors as virtual, object slicing,
multiple inheritance’s diamond problem.
• For Java: exposing internal collections, careless use of static fields (global state), overuse of
reflection, ignoring thread safety.

7. Practice Explaining Concepts Clearly

• Interviews often test not just your code, but your ability to communicate technical ideas.
Practice explaining polymorphism to someone who knows only procedural programming, or
walk through how a virtual table lookup happens in memory.

8. Use Realistic Examples

• Spend some time sketching out small class hierarchies or systems (like the Task Management
example above). This helps solidify theoretical knowledge by applying it in a design context.

Final Tips Before Your Interview

• Review Standard Library/Framework Classes: For C++, know how std::vector, std::string,
and std::unique_ptr relate to OOP. For Java, be familiar with java.util collections, java.lang.Object
methods (e.g., toString(), equals(), hashCode()), and where inheritance/polymorphism come
into play.

• Whiteboard Coding: Practice sketching class definitions, inheritance hierarchies, and code
fragments on a whiteboard (or paper). Many interviews require writing code by hand.

• Debug Common Interview Snippets: Look for typical small OOP “gotchas”—for example, a
base class method not being declared virtual, or a missing override annotation that leads to
an unintended overload.

• Ask Clarifying Questions: If an interviewer says, “Design a class hierarchy for geometric
shapes,” clarify expected operations (area, perimeter, drawing, serialization) before jumping in.
Good design often hinges on understanding requirements.

• Time Management: In a timed setting, outline your solution first (class names, main methods,
relationships), then fill in details. This shows the interviewer you have a structured approach.

49

You might also like