OOP Concepts — Abstraction, Encapsulation, Inheritance, Polymorphism
2837 words
14 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# OOP Concepts — Abstraction, Encapsulation, Inheritance, Polymorphism ## 🎯 Learning Objectives By the end of this topic, you will be able to: - Explain the four pillars of OOP with concrete analogies - Distinguish between abstraction and encapsulation - Describe how subtyping enables polymorphism - Understand how...

OOP Concepts — Abstraction, Encapsulation, Inheritance, Polymorphism
🎯 Learning Objectives
By the end of this topic, you will be able to:
- Explain the four pillars of OOP with concrete analogies
- Distinguish between abstraction and encapsulation
- Describe how subtyping enables polymorphism
- Understand how dynamic dispatch works at runtime
- Compare Java's OOP implementation with Python's
📋 Prerequisites
- Java Introduction (01-java-introduction.md): Basic Java syntax
- Memory Model (03-memory-model.md): References, heap allocation
- BSCS1002 — Python: You've used classes and objects in Python
1. Intuition: Why Object-Oriented Programming?
1.1 What Problem Does OOP Solve?
Imagine building a house. You could:
- Assembly-line approach (procedural): Have a single huge list of instructions: "Cut board A, nail to board B, attach door C, paint wall D..."
- Modular approach (OOP): Build rooms as separate modules. Each room has its own door, windows, and walls. You can reuse the "Bedroom" design in multiple houses, customize it, and fix a leaky window without touching the rest of the house. OOP organizes code around objects (things in the real world) rather than actions. This makes large programs easier to understand, maintain, and extend.
1.2 The Four Pillars (Mental Model)
Think of a restaurant kitchen:
| OOP Pillar | Kitchen Analogy | Meaning in Code |
|---|---|---|
| Abstraction | The menu lists dishes, not recipes | Hide complex implementation; expose simple interface |
| Encapsulation | Ingredients are in labeled containers, not scattered | Bundle data + methods; protect data from outside access |
| Inheritance | "Chef" is a specialized "Employee" | Create new classes based on existing ones |
| Polymorphism | Any chef can cook, but each has their specialty | Same method name, different behavior by type |
2. Abstraction — Hiding Complexity
2.1 Intuition
You drive a car using the steering wheel and pedals (interface). You don't need to understand how the engine, transmission, and fuel injection work. The complexity is hidden — that's abstraction.
2.2 In Code
Abstraction means exposing only the essential details and hiding the internal implementation:
java// WHAT it does (accessible interface) public class CoffeeMachine { public Coffee brew(EspressoOrder order) { ... } public Coffee brew(LatteOrder order) { ... } // HOW it does it (hidden from users) private void heatWater() { ... } private void grindBeans() { ... } private void pressurize() { ... } }
Users of
CoffeeMachine call brew() and get coffee. They don't need to know about water heating or bean grinding.2.3 Java Mechanisms for Abstraction
- Abstract classes (
abstract class): Partially implemented classes that cannot be instantiated - Interfaces (
interface): Fully abstract contracts that classes implement - Methods: Public methods define the API; private methods are implementation details
3. Encapsulation — Data Protection
3.1 Intuition
A bank account has a balance that only the account holder can change through specific operations (deposit, withdraw). You can't just walk in and scribble a new number on the teller's screen. The balance is encapsulated — protected from direct access.
3.2 In Code
Encapsulation bundles data (fields) with the methods that operate on that data, and restricts direct access:
javapublic class BankAccount { private double balance; // Private — cannot access from outside public BankAccount(double initialBalance) { if (initialBalance >= 0) { this.balance = initialBalance; } } public void deposit(double amount) { if (amount > 0) { balance += amount; } } public boolean withdraw(double amount) { if (amount > 0 && balance >= amount) { balance -= amount; return true; } return false; } public double getBalance() { // Controlled access via getter return balance; } }
3.3 The private Keyword
private fields can only be accessed within the same class. This prevents:- Setting an invalid value (e.g., negative balance)
- Dependencies on internal implementation (you can change the internal structure without affecting external code)
3.4 Getters and Setters
The standard Java pattern for controlled access:
javapublic class Person { private String name; private int age; // Getter public String getName() { return name; } // Setter with validation public void setAge(int age) { if (age >= 0 && age <= 150) { this.age = age; } } }
4. Inheritance — "Is-a" Relationship
4.1 Intuition
A Dog is a Mammal. A Mammal is an Animal. Each level inherits properties from the level above. A dog breathes (like all animals) and has hair (like all mammals), but also barks (specific to dogs).
4.2 In Code
java// Base class (parent) public class Animal { protected String name; public Animal(String name) { this.name = name; } public void eat() { System.out.println(name + " is eating."); } public void sleep() { System.out.println(name + " is sleeping."); } } // Derived class (child) — Dog IS-A Animal public class Dog extends Animal { public Dog(String name) { super(name); // Call parent constructor } public void bark() { System.out.println(name + " says Woof!"); } @Override public void eat() { // Override parent's method System.out.println(name + " is eating dog food."); } }
4.3 Key Concepts
extends keyword: Indicates that a class inherits from another:javapublic class Dog extends Animal { ... }
super keyword: References the parent class:javasuper(name); // Call parent constructor super.eat(); // Call parent method
@Override annotation: Documents that a method replaces a parent method. The compiler will warn if you mis-type the method name.4.4 Constructor Chaining
When you create a
Dog, Java calls ALL constructors up the chain:javaDog d = new Dog("Buddy"); // 1. Animal("Buddy") runs first (super class) // 2. Dog("Buddy") runs next (subclass)
4.5 Method Overriding
A subclass can provide its own implementation of a parent method. The
@Override annotation tells the compiler:javapublic class Cat extends Animal { @Override public void eat() { System.out.println(name + " is eating cat food."); } }
4.6 final Keyword on Classes and Methods
final class: Cannot be extended (e.g.,String,Integer)final method: Cannot be overridden
javapublic final class FinalClass { } // Cannot be inherited public class Parent { public final void cannotOverride() { } }
5. Polymorphism — Many Forms
5.1 Intuition
"Press the brake pedal" — in any car, pressing the brake slows the car. But the internal mechanism differs: disc brakes, drum brakes, regenerative brakes (electric cars). The interface is the same (press pedal), but the implementation differs (polymorphism).
5.2 Compile-Time Polymorphism (Method Overloading)
Same method name, different parameters — resolved at compile time:
javapublic class Calculator { public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } }
The compiler chooses the right method based on argument types and count.
5.3 Runtime Polymorphism (Dynamic Dispatch)
The JVM decides which method to call at runtime based on the actual object type, not the reference type:
javaAnimal myPet = new Dog("Buddy"); // Reference is Animal, actual is Dog myPet.eat(); // Calls Dog's eat() — dynamic dispatch!
(Diagram)
5.4 Upcasting and Downcasting
Upcasting (safe, implicit):
javaDog dog = new Dog("Buddy"); Animal animal = dog; // Upcast — Dog → Animal (always safe)
Downcasting (risky, explicit):
javaAnimal animal = new Dog("Buddy"); Dog dog = (Dog) animal; // OK — animal IS a Dog Cat cat = (Cat) animal; // Compiles but ClassCastException at runtime!
Use
instanceof to check before downcasting:javaif (animal instanceof Dog) { Dog dog = (Dog) animal; // Safe }
5.5 Polymorphism Enables Flexibility
javapublic class Zoo { public void feedAll(Animal[] animals) { for (Animal a : animals) { a.eat(); // Each animal eats its own way } } } // Usage Animal[] animals = {new Dog("Buddy"), new Cat("Kitty"), new Dog("Max")}; Zoo zoo = new Zoo(); zoo.feedAll(animals); // Buddy is eating dog food. // Kitty is eating cat food. // Max is eating dog food.
6. Understanding static Members
6.1 Instance vs Static
| Instance (non-static) | Static | |
|---|---|---|
| Belongs to | Each object separately | The class itself |
| Memory | Heap (per object) | Method area (one copy) |
| Accessed via | Object reference (obj.field) | Class name (ClassName.field) |
| Can access other | Instance and static members | Only static members directly |
javapublic class Student { public String name; // Instance variable public static int count; // Class variable (one shared value) public static final String COLLEGE = "IIT Madras"; // Constant public Student(String name) { this.name = name; count++; // Increment shared counter } } // Usage Student s1 = new Student("Alice"); Student s2 = new Student("Bob"); System.out.println(Student.count); // 2 — shared across instances System.out.println(s1.COLLEGE); // IIT Madras
7. Java vs Python: OOP Comparison
| Feature | Java | Python |
|---|---|---|
| Everything is an object? | Primitives are not | Yes (even ints are objects) |
| Multiple inheritance | Not supported for classes (interfaces only) | Supported (with MRO) |
| Access modifiers | private, protected, public, package-private | _ (convention), __ (name mangling) |
| Overloading | Yes (compile-time) | Not directly (default args, *args) |
| Overriding | Explicit @Override | Automatic (duck typing) |
| Dynamic dispatch | Virtual methods by default | All methods are virtual |
| Abstract classes | abstract class keyword | ABC from abc module |
| Interfaces | interface keyword | Informal (duck typing) |
this/self | this (implicit) | self (explicit first parameter) |
| Constructor | Same name as class | __init__ |
| Destructor | finalize() (deprecated) | __del__ |
8. Common Pitfalls
Pitfall 1: Confusing Overloading with Overriding
javapublic class Parent { public void doSomething(int x) { } } public class Child extends Parent { public void doSomething(double x) { } // OVERLOADING, not overriding! }
Why: Different parameter type → overloading. The child has two methods: the inherited
doSomething(int) and a new doSomething(double). Fix: Use @Override annotation — it will tell you if you're actually overriding or accidentally overloading.Pitfall 2: Accessing Private Members in Subclass
javapublic class Parent { private int secret = 42; } public class Child extends Parent { public void show() { System.out.println(secret); // Compile error! } }
Why:
private members are not inherited. They exist in the object but are only accessible within the declaring class. Fix: Use protected if the subclass should access it.Pitfall 3: instanceof with null
javaDog dog = null; if (dog instanceof Dog) { // Returns false, not NullPointerException! System.out.println("It's a dog"); }
Why:
instanceof elegantly handles null — if the reference is null, it returns false. This is actually useful for safe checks.Pitfall 4: Static Methods Cannot Be Overridden
javapublic class Parent { public static void greet() { System.out.println("Parent"); } } public class Child extends Parent { public static void greet() { // This HIDES, not overrides System.out.println("Child"); } } // Usage Parent p = new Child(); p.greet(); // Prints "Parent", not "Child"!
Why: Static methods are resolved at compile-time based on reference type, not runtime object type. This is called method hiding, not overriding.
Pitfall 5: Forgetting super() Call
javapublic class Parent { public Parent(int x) { System.out.println("Parent: " + x); } } public class Child extends Parent { public Child() { // Implicit super() — but Parent has no no-arg constructor! } }
Why: If the parent class has no no-arg constructor, the child must explicitly call
super(value) as the first statement in its constructor. Fix: Add super(0); as the first statement in Child().9. Practice Questions
Q1: What is the output?javaclass A { public void show() { System.out.println("A"); } } class B extends A { public void show() { System.out.println("B"); } } public class Test { public static void main(String[] args) { A obj = new B(); obj.show(); } }Answer:BDynamic dispatch: Even though the reference type isA, the actual object isB. Java determines at runtime whichshow()to call based on the actual object type (B). Q2: What is wrong with this code?javapublic class Main { public static void main(String[] args) { Animal a = new Animal("Generic"); } } abstract class Animal { protected String name; public Animal(String name) { this.name = name; } public abstract void sound(); }Answer:Animalis abstract, so you cannot instantiate it withnew Animal(...). Abstract classes can only be extended by concrete subclasses. Fix: Create a concrete subclass or removeabstractfrom the class declaration. Q3: Explain the outputjavaclass Parent { int x = 10; } class Child extends Parent { int x = 20; } public class Test { public static void main(String[] args) { Parent p = new Child(); System.out.println(p.x); Child c = new Child(); System.out.println(c.x); } }Answer:10then20Fields are not polymorphic! Java resolves field access at compile-time based on reference type, not runtime type.p.xusesParent.x = 10becausepis declared asParent.c.xusesChild.x = 20becausecis declared asChild. This is called field shadowing, not overriding. Q4: What is encapsulation and why is it important?Answer: Encapsulation is the bundling of data (fields) with methods that operate on that data, while restricting direct access to the internal state. It's important because:
- Protection: Prevents invalid state (e.g., negative age, empty name)
- Flexibility: Internal implementation can change without affecting external code
- Maintainability: Dependencies are clearly defined through the public API
- Testing: Components can be tested in isolation
In Java, encapsulation is achieved throughprivatefields withpublicgetter/setter methods. Q5: What is the difference between abstraction and encapsulation?Answer:
| Abstraction | Encapsulation | |
|---|---|---|
| What | Hiding complexity, showing essential features | Bundling data + methods, protecting data |
| Goal | Reduce complexity by hiding details | Prevent unauthorized access |
| How | Abstract classes, interfaces | private fields, getters/setters |
| Focus | What an object does | How an object's state is managed |
| Analogy | Car steering wheel (interface) | Car engine casing (protection) |
Q6: What is the output? Explain the concept.javaclass MathUtils { public static int square(int x) { return x * x; } } public class Test { public static void main(String[] args) { int result = MathUtils.square(5); System.out.println(result); } }Answer:25Thesquaremethod isstatic, meaning it belongs to the class, not to any instance. It is called using the class name (MathUtils.square(5)) without creating an object. Static methods can only access other static members directly. Q7: Why does this code throw ClassCastException?javaAnimal a = new Dog(); Cat c = (Cat) a;Answer: TheAnimalreferenceaactually points to aDogobject. Downcasting it toCatasks the JVM to treat a Dog as a Cat, which is impossible.instanceofcheck would prevent this:javaif (a instanceof Cat) { Cat c = (Cat) a; // Safe } else { System.out.println("Not a cat"); }Q8: Which OOP pillar does method overriding implement?Answer: Polymorphism — specifically runtime polymorphism (dynamic dispatch). Overriding allows a subclass to provide its own implementation of a method defined in the parent class. When called through a parent reference, the JVM determines at runtime which version to execute based on the actual object type.Related: Method overloading implements compile-time polymorphism (also called static polymorphism or ad-hoc polymorphism). Q9: What doessuperdo in a constructor?Answer:super()calls the parent class constructor. It must be the first statement in a constructor. If you don't explicitly writesuper(), the compiler inserts an implicitsuper()call to the no-arg constructor. If the parent class has no no-arg constructor, you must explicitly callsuper(args).javapublic Child(int x) { super(x); // Must be first statement // Other initialization... }Q10: Can you override a private method? A static method? A final method?Answer:
- Private method: No. Private methods are not visible to subclasses, so they cannot be overridden (they can be redeclared, but that's a new method, not an override).
- Static method: No. Static methods are hidden, not overridden. The method called depends on the reference type, not the object type.
- Final method: No.
finalexplicitly prevents overriding.
📐 Key Concepts
| Concept | Description | Java Keyword |
|---|---|---|
| Abstraction | Hide complexity, expose essentials | abstract, interface |
| Encapsulation | Protect data, bundle fields + methods | private, getters/setters |
| Inheritance | Create "is-a" hierarchies | extends |
| Polymorphism | Same interface, different implementations | @Override, virtual dispatch |
| Dynamic dispatch | Runtime method resolution based on object type | Implicit (all non-final, non-static methods) |
| Static binding | Compile-time method resolution | static, final, private |
| Upcasting | Treating subclass as superclass (safe) | Implicit |
| Downcasting | Treating superclass as subclass (risky) | Explicit (Type) + instanceof |
🔗 Cross-References
- Next: Arrays — first concrete data structure
- Related: Polymorphism, Abstract Classes & Interfaces
- Python Comparison: BSCS1002 — OOP in Python
- Reference: Oracle Java Tutorials — Object-Oriented Programming Concepts Join Discord Previous1.4 Control FlowNext2.1 Arrays