Object-Oriented Programming (OOP) is a programming paradigm based on the concept of objects that contain data and methods. It helps in building scalable, reusable, and maintainable software.
Example (Java):
class Car {
String brand;
int year;
void drive() {
System.out.println("Driving " + brand);
}
}
public class Main {
public static void main(String[] args) {
Car car1 = new Car();
car1.brand = "Toyota";
car1.year = 2022;
car1.drive();
}
}
abstract class Animal {
abstract void sound();
}
class Dog extends Animal {
void sound() {
System.out.println("Woof Woof");
}
}
class BankAccount {
private double balance;
public void deposit(double amount) {
balance += amount;
}
public double getBalance() {
return balance;
}
}
class Vehicle {
void start() {
System.out.println("Vehicle started");
}
}
class Car extends Vehicle {
void honk() {
System.out.println("Car honks");
}
}
Single Inheritance: The single child class inherits characteristics of the single-parent class.Multiple Inheritance: One class inherits features of more than one base class and is not supported in Java, but the class can implement more than one interface.Multilevel Inheritance: A class can inherit from a derived class making it a base class for a new class, for example, a Child inherits behavior from his father, and the father has inherited characteristics from his father.Hierarchical Inheritance: Multiple subclasses inherit one class.Hybrid Inheritance: This is a combination of single and multiple inheritance.Compile-time (Method Overloading) Runtime (Method Overriding)
class Shape {
void draw() {
System.out.println("Drawing Shape");
}
}
class Circle extends Shape {
void draw() {
System.out.println("Drawing Circle");
}
}
class Engine {
void start() {
System.out.println("Engine starts");
}
}
class Car {
Engine engine = new Engine();
void drive() {
engine.start();
System.out.println("Car drives");
}
}
interface Flyable {
void fly();
}
class Bird implements Flyable {
public void fly() {
System.out.println("Bird flies");
}
}
Definition: Multiple methods in the same class share the same name but differ in parameter lists (type, number, or order). Compile-time polymorphism.
Example (Java):
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // 5
System.out.println(calc.add(1, 2, 3)); // 6
System.out.println(calc.add(1.5, 2.5)); // 4.0
}
}
Definition: A subclass provides a specific implementation of a method that is already defined in its superclass. Runtime polymorphism (dynamic dispatch).
Example (Java):
class Animal {
void sound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Woof Woof");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Animal();
Animal a2 = new Dog(); // reference type Animal, runtime object Dog
a1.sound(); // Some generic animal sound
a2.sound(); // Woof Woof (Dog's overridden method called)
}
}
Notes:
Quick examples (Java):
// Constructor + this example
class Person {
String name;
int age;
// No-arg constructor calls parameterized constructor
Person() {
this("Unknown", 0); // calls Person(String,int)
}
// Parameterized constructor
Person(String name, int age) {
this.name = name; // this disambiguates field vs parameter
this.age = age;
}
void info() {
System.out.println(name + " - " + age);
}
}
class Employee extends Person {
String company;
// super(...) calls superclass constructor; must be first statement
Employee(String name, int age, String company) {
super(name, age); // initialize Person part
this.company = company;
}
void info() {
super.info(); // call superclass method
System.out.println("Company: " + company);
}
public static void main(String[] args) {
Person p = new Person("Alice", 30);
p.info(); // Alice - 30
Employee e = new Employee("Bob", 25, "Acme");
e.info();
// Output:
// Bob - 25
// Company: Acme
}
}
Memorize cheat-sheet:
Example â static binding (overloading):
class OverloadExample {
void show(int x) { System.out.println("int: " + x); }
void show(String s) { System.out.println("String: " + s); }
public static void main(String[] args) {
OverloadExample o = new OverloadExample();
o.show(10); // compile-time selects show(int)
o.show("hello"); // compile-time selects show(String)
}
}
Example â dynamic binding (overriding):
class Animal {
void sound() { System.out.println("Animal sound"); }
}
class Dog extends Animal {
@Override
void sound() { System.out.println("Woof"); }
}
public class BindDemo {
public static void main(String[] args) {
Animal a = new Dog(); // reference type Animal, object Dog
a.sound(); // Dog.sound() called at runtime (dynamic binding)
}
}
Cheat: Overloading = static/compile-time. Overriding = dynamic/runtime.
Shallow clone example:
class Address {
String city;
Address(String city) { this.city = city; }
}
class Person implements Cloneable {
String name;
Address addr;
Person(String name, Address addr) { this.name = name; this.addr = addr; }
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // shallow copy
}
public static void main(String[] args) throws Exception {
Address a1 = new Address("NY");
Person p1 = new Person("Alice", a1);
Person p2 = (Person) p1.clone(); // shallow: p1.addr == p2.addr
p2.addr.city = "LA";
System.out.println(p1.addr.city); // prints "LA" â shared reference
}
}
Deep clone example (clone referenced objects too):
class Address implements Cloneable {
String city;
Address(String city) { this.city = city; }
@Override protected Address clone() throws CloneNotSupportedException { return (Address) super.clone(); }
}
class Person implements Cloneable {
String name;
Address addr;
Person(String name, Address addr) { this.name = name; this.addr = addr; }
@Override
protected Person clone() throws CloneNotSupportedException {
Person copy = (Person) super.clone();
copy.addr = this.addr.clone(); // deep clone of address
return copy;
}
}
Tip: Prefer copy constructors or serialization for complex deep copies.
Types:
Member inner class example:
class Outer {
int x = 10;
class Inner {
void show() { System.out.println("x = " + x); } // can access outer instance
}
public static void main(String[] args) {
Outer o = new Outer();
Outer.Inner i = o.new Inner();
i.show(); // x = 10
}
}
Static nested class example:
class Outer {
static class Nested {
static void info() { System.out.println("static nested"); }
}
public static void main(String[] args) {
Outer.Nested.info();
}
}
Anonymous class example (Runnable):
public class AnonDemo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous Runnable");
}
});
t.start();
}
}
Memorize:
Generics in object-oriented programming (OOP) allow developers to create classes, interfaces, and methods that can operate on different data types while providing type safety at compile time. This enhances code reusability and flexibility, enabling the same code to work with various types without the need for casting or duplication.
Simple generic class example:
class Box<T> {
private T value;
Box(T value) { this.value = value; }
T get() { return value; }
void set(T value) { this.value = value; }
public static void main(String[] args) {
Box<String> b = new Box<>("hello");
String s = b.get(); // no cast needed
System.out.println(s);
}
}
Generic method and bounded type parameter:
class Utils {
// Generic method
static <T> void printArray(T[] arr) {
for (T e : arr) System.out.println(e);
}
// Bounded type parameter (only Number or its subclasses)
static <T extends Number> double sum(T a, T b) {
return a.doubleValue() + b.doubleValue();
}
}
Cheat:
Simple Java example (constructor injection):
interface MessageService {
void send(String msg);
}
class EmailService implements MessageService {
public void send(String msg) { System.out.println("Email: " + msg); }
}
class UserController {
private final MessageService service;
// Dependency provided via constructor (injected)
UserController(MessageService service) {
this.service = service;
}
void notifyUser(String msg) {
service.send(msg);
}
public static void main(String[] args) {
MessageService svc = new EmailService(); // wiring
UserController ctrl = new UserController(svc); // inject
ctrl.notifyUser("Welcome!");
}
}
Notes:
Class diagram basics:
| Class box: three compartments â Name | Attributes | Methods |
| Inheritance: solid line with closed hollow arrow (A â | > B) |
Example (textual UML for simple model):
Car
Engine
Relationships:
Quick tips to draw useful UML:
Cheat:
Design patterns solve common design problems. Grouped by purpose.
// Simple thread-safe lazy singleton
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
interface Shape { void draw(); }
class Circle implements Shape { public void draw(){ System.out.println("Circle"); } }
class Square implements Shape { public void draw(){ System.out.println("Square"); } }
class ShapeFactory {
static Shape create(String type) {
switch(type) {
case "circle": return new Circle();
case "square": return new Square();
default: throw new IllegalArgumentException("Unknown");
}
}
}
class Person {
private final String name; private final int age; private final String city;
private Person(Builder b){ name=b.name; age=b.age; city=b.city; }
static class Builder {
String name; int age; String city;
Builder name(String n){ this.name=n; return this; }
Builder age(int a){ this.age=a; return this; }
Builder city(String c){ this.city=c; return this; }
Person build(){ return new Person(this); }
}
}
// Usage: Person p = new Person.Builder().name("A").age(30).city("X").build();
interface Power { int getVoltage(); }
class USPower { int voltage() { return 120; } }
class USPowerAdapter implements Power {
private final USPower up = new USPower();
public int getVoltage(){ return up.voltage(); }
}
interface Component { void show(); }
class Leaf implements Component { private String name; Leaf(String n){name=n;} public void show(){ System.out.println(name); } }
class Composite implements Component {
private List<Component> children = new ArrayList<>();
void add(Component c){ children.add(c); }
public void show(){ for(Component c: children) c.show(); }
}
interface Coffee { double cost(); String desc(); }
class SimpleCoffee implements Coffee { public double cost(){return 2; } public String desc(){return "Coffee";} }
abstract class CoffeeDecorator implements Coffee {
protected final Coffee c; CoffeeDecorator(Coffee c){this.c=c;}
public double cost(){ return c.cost(); } public String desc(){ return c.desc(); }
}
class Milk extends CoffeeDecorator { Milk(Coffee c){super(c);} public double cost(){return super.cost()+0.5;} public String desc(){return super.desc()+" + milk";} }
interface Observer { void update(String msg); }
class Subject {
private List<Observer> obs = new ArrayList<>();
void add(Observer o){ obs.add(o); }
void notifyAllObservers(String m){ for(Observer o: obs) o.update(m); }
}
class ConcreteObserver implements Observer { private String name; ConcreteObserver(String n){name=n;} public void update(String msg){ System.out.println(name + " got: " + msg); } }
interface PaymentStrategy { void pay(int amount); }
class CreditCard implements PaymentStrategy { public void pay(int a){ System.out.println("Paid " + a + " by card"); } }
class PayPal implements PaymentStrategy { public void pay(int a){ System.out.println("Paid " + a + " by PayPal"); } }
class Checkout {
private PaymentStrategy strategy;
Checkout(PaymentStrategy s){ this.strategy = s; }
void doPay(int amt){ strategy.pay(amt); }
}
interface Command { void execute(); }
class Light { void on(){ System.out.println("Light on"); } void off(){ System.out.println("Light off"); } }
class LightOnCommand implements Command {
private Light light;
LightOnCommand(Light l){ this.light = l; }
public void execute(){ light.on(); }
}
class Remote {
private Command slot;
void setCommand(Command c){ this.slot = c; }
void press(){ if(slot!=null) slot.execute(); }
}
Cheat-sheet:
class ReportGenerator {
String generate() { /* build report data */ return "report"; }
}
class ReportPrinter {
void print(String r) { System.out.println(r); }
}
interface Shape { double area(); }
class Circle implements Shape { double radius; public double area(){ return Math.PI*radius*radius; } }
class Rectangle implements Shape { double w,h; public double area(){ return w*h; } }
// New shapes added by implementing Shape, no change to existing code.
interface FlyingBird { void fly(); }
class Sparrow implements FlyingBird { public void fly(){ System.out.println("fly"); } }
class Ostrich { /* does not implement FlyingBird */ }
interface Work { void doWork(); }
interface Eat { void eat(); }
class Worker implements Work, Eat { public void doWork(){} public void eat(){} }
interface MessageService { void send(String msg); }
class EmailService implements MessageService { public void send(String m){ System.out.println("Email:"+m); } }
class Notification {
private final MessageService svc;
Notification(MessageService svc){ this.svc = svc; } // depends on abstraction
void notify(String m){ svc.send(m); }
}
Cheat-sheet:
Type System āĻā§ āĻāĻŋāύāĻŋāϏ?
##OOP-āĻ Types āĻā§āύ āĻāϤ āĻāϰā§āϰāĻŋ
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "āĻā§āĻ āĻā§āĻ"
class Cat(Animal):
def speak(self):
return "āĻŽāĻŋāĻ āĻŽāĻŋāĻ"
Interface āĻāϰ Abstraction Type system interface āĻŦāĻžāύāĻžāϤ⧠help āĻāϰā§, āϝā§āĻāĻž different classes-āĻāϰ āĻŽāϧā§āϝ⧠āĻāĻāĻāĻž contract āϤā§āϰāĻŋ āĻāϰā§āĨ¤ āĻāϤ⧠code āĻāϰ⧠organized āĻāϰ maintainable āĻšāϝāĻŧā§ āϝāĻžāϝāĻŧāĨ¤
IDE Support āĻāϰ Autocomplete āϝāĻĻāĻŋ strong type system āĻĨāĻžāĻā§ āϤāĻžāĻšāϞ⧠modern IDEs āĻ āύā§āĻ better autocomplete, refactoring āĻāϰ error detection āĻĻāĻŋāϤ⧠āĻĒāĻžāϰā§āĨ¤ āĻāϤ⧠developer-āĻĻā§āϰ productivity multifold āĻŦā§āĻĄāĻŧā§ āϝāĻžāϝāĻŧāĨ¤
Documentation āĻāϰ Readability Type declarations code-āĻāϰ āĻāĻāϧāϰāύā§āϰ self-documentation āĻšāĻŋāϏā§āĻŦā§ āĻāĻžāĻ āĻāϰā§āĨ¤ āĻ āύā§āϝ developers āϝāĻāύ code āĻĒāĻĄāĻŧā§, āϤāĻāύ āϏāĻšāĻā§āĻ āĻŦā§āĻāϤ⧠āĻĒāĻžāϰ⧠āĻā§āύ method āĻā§ āϧāϰāύā§āϰ data āύā§āϝāĻŧ āĻāϰ āĻā§ return āĻāϰā§āĨ¤
OOP languages āĻĻā§āĻ āĻāĻžāĻā§ āĻĒāĻĄāĻŧā§:
Static Typing (Java, C++, C#): Compile time-āĻ type check āĻšāϝāĻŧāĨ¤ āĻŦā§āĻļāĻŋ safe āĻāĻŋāύā§āϤ⧠āĻāĻāĻā§ verbose āϞāĻžāĻā§āĨ¤
Dynamic Typing (Python, Ruby, JavaScript): Runtime-āĻ type check āĻšāϝāĻŧāĨ¤ āĻŦā§āĻļāĻŋ flexible āĻāĻŋāύā§āϤ⧠runtime errors-āĻāϰ risk āĻĨāĻžāĻā§āĨ¤
āĻāϧā§āύāĻŋāĻ languages āϝā§āĻŽāύ TypeScript āĻāϰ Python (type hints āϏāĻš) gradual typing support āĻāϰā§āĨ¤ āĻāĻāĻž dynamic language-āĻāϰ flexibility āĻāϰ static language-āĻāϰ safety āĻĻā§āĻā§āĻ āĻĻā§āϝāĻŧāĨ¤
class Person {
constructor(public name: string, public age: number) {}
greet(): string {
return `āĻāĻŽāĻžāϰ āύāĻžāĻŽ ${this.name}, āĻāĻŽāĻžāϰ āĻŦāϝāĻŧāϏ ${this.age}`;
}
}
Extends: A class can extend another class (child extending parent by inheriting his characteristics). Interface as well inherit (using keyword extends) another interface.
Implements: A class can implement an interface
Extends:Sub class extending super class may not override all of the super class methods
Implements:Class implementing interface has to implement all the methods of the interface.
Extends:Class can only extend a single super class.
Implements: Class can implement any number of interfaces.
Default access modifier is without any access specifier data members, class and methods, and are accessible within the same package.Private access modifiers are marked with the keyword private, and are accessible only within class, and not even accessible by class from the same package.Protected access modifiers can be accessible within the same package or subclasses from different packages.Public access modifiers are accessible from everywhere.Compile Time Polymorphism: Call is resolved by a compiler in compile-time polymorphism.
Runtime Polymorphism: Call is not resolved by the compiler in runtime polymorphism.
Compile Time Polymorphism:It is also known as static binding and method overloading.
Runtime Polymorphism: It is also known as dynamic, late, and method overriding.
Compile Time Polymorphism: Same name methods with different parameters or methods with the same signature and different return types are compile-time polymorphism.
Runtime Polymorphism: Same name method with the same parameters or signature associated in different classes are called method overriding.