☕ Java OOP — Complete Series

Encapsulation in Java
Complete Deep Dive

Theory, Memory Model, Access Modifiers, Getters/Setters, Real-World Patterns aur 20+ Practice Programs — sab kuch ek jagah, depth mein.

10
Chapters
30+
Code Examples
20+
Practice Programs
4
Access Modifiers
Full
Theory Covered
01

Encapsulation Kya Hai?

Complete Theory · OOP Pillar · Why Encapsulate · Real Analogy · Problem Without It

📖 Core Definition

Encapsulation — Data aur Behavior Ko Ek Saath Bandhna

Encapsulation Object-Oriented Programming (OOP) ka ek fundamental pillar hai jisme data (variables/fields) aur us data par kaam karne wale methods ko ek single unit — yani class — ke andar band kiya jaata hai.

Lekin sirf ek unit mein rakhna kaafi nahi. Encapsulation ka dusra aur sabse zaroori part hai — us data ko bahar se directly access hone se rokna (private fields) aur sirf controlled methods (getters/setters) ke through access dena.

Is concept ke 2 parts hain:

  • Data Hiding: Internal data ko private rakho — koi bhi class seedha access na kar sake
  • Data Binding: Related data aur methods ko ek class mein rakh do — sab logically connected ho
Formal Definition: Encapsulation is the mechanism of wrapping data (fields) and the methods that operate on that data into a single unit (class), while restricting direct access to the internal state and requiring access through well-defined public interfaces (getter/setter methods).
🔒

Encapsulation

Data hide karo, controlled access do

🧬

Inheritance

Parent se properties lena

🎭

Polymorphism

Ek naam, alag alag behavior

🏗️

Abstraction

Implementation chupaana, interface dikhana

💡 Real-Life Analogy — ATM Machine ATM ek perfect encapsulation example hai. Tumhare paas ATM ka interface hai (buttons, screen, card slot). Tum PIN daalte ho, withdrawal maangte ho — lekin andar ki banking logic, database connection, encryption — sab hidden hai tumse. Tum seedha bank database ko access nahi kar sakte. Ye hi encapsulation hai!

Aur ek: Capsule tablet — andar medicine hai (data), lekin upar coating hai (private modifier). Tum seedha medicine ko touch nahi karte, coating ke through liya jaata hai.
⚠️ Problem Without Encapsulation

Bina Encapsulation Ke Kya Hota Hai?

Sochte hain koi BankAccount class hai jisme balance field public hai:

  • Direct Corruption: Koi bhi class likh sakta hai account.balance = -99999; — negative balance! No validation possible.
  • No Audit Trail: Pata nahi kaun, kab, kyun balance change kiya — debugging nightmare
  • Tight Coupling: Agar field ka naam badlna ho (balanceaccountBalance) to saari jagah change karna padega
  • Thread Safety Issues: Multiple threads seedha field modify karen to race conditions
  • Business Logic Bypass: Minimum balance, overdraft rules — sab koi bhi bypass kar sakta hai
❌ Without Encapsulation (Dangerous)
class BankAccount {
    // PUBLIC — koi bhi change kar sakta!
    public String owner;
    public double balance;
    public String pin;    // 😱 PIN public!
}

// Anywhere in codebase:
BankAccount acc = new BankAccount();
acc.balance = -50000;  // No check!
acc.pin = "0000";      // Hack!
System.out.println(acc.pin); // Leak!
✅ With Encapsulation (Safe)
class BankAccount {
    // PRIVATE — bahar access impossible
    private String owner;
    private double balance;
    private String pin;

    public void deposit(double amt) {
        if (amt > 0) balance += amt;
    }
    // PIN never exposed publicly!
}
🔐

Data Security

Sensitive data (PIN, password, SSN) bahar expose nahi hota. Unauthorized access impossible.

Validation

Setter mein rules laga sako — negative age, invalid email, overdraft — sab control mein.

🔧

Flexibility

Internal implementation change karo bina interface todhe. Users ko pata bhi nahi chalega.

🧪

Testability

Encapsulated class isolate karke test karna easy hai — mocking aur unit testing simple.

📦

Modularity

Har class apna kaam kare — SRP (Single Responsibility Principle) naturally follow hoti hai.

🔄

Maintainability

Change ek jagah karo, poora codebase safe rahega. Tight coupling avoid hoti hai.

🌐 Encapsulation in Backend Production

Real-World Backend Uses

  • Spring Boot Entities: JPA entities mein fields private hote hain, Lombok @Getter @Setter se expose
  • DTO (Data Transfer Objects): API response mein sirf allowed fields expose karo — sensitive data hide
  • Service Layer: Business logic encapsulate karo — Controller directly database touch nahi karta
  • Builder Pattern: Complex object creation mein encapsulation — UserBuilder.email().name().build()
  • Configuration: DB credentials, API keys — encapsulate karo, directly access mat karo
  • Thread Safety: synchronized setters se thread-safe encapsulated state manage karo
02

Access Modifiers

private · default · protected · public · Scope Rules · Complete Table

📖 Complete Theory

Access Modifiers — Visibility Control

Java mein 4 access modifiers hain jo control karte hain ki koi class, method, ya field kahin se accessible hai ya nahi. Ye encapsulation ka technical implementation hain — bina inke encapsulation possible nahi.

Access modifiers sirf class members (fields, methods, constructors) par hi nahi, classes par bhi lagate hain (sirf public ya default — package-private).

Rule of Thumb (Principle of Least Privilege): Hamesha sabse restrictive modifier se shuru karo. private pehli choice, phir zaroorat padne par loosening karo. Kabhi bhi sochke public mat kar do.

🔴 private

Same Class
Same Package
Subclass
Everywhere

🔵 default (no keyword)

Same Class
Same Package
Subclass (diff pkg)
Everywhere

🟡 protected

Same Class
Same Package
Subclass (diff pkg)
Everywhere

🟢 public

Same Class
Same Package
Subclass (diff pkg)
Everywhere
ModifierSame ClassSame PackageSubclass (diff pkg)Other ClassesUse For
private ✅ Yes ❌ No ❌ No ❌ No Fields, helper methods
default (none) ✅ Yes ✅ Yes ❌ No ❌ No Package-internal classes
protected ✅ Yes ✅ Yes ✅ Yes ❌ No Inheritance, framework base classes
public ✅ Yes ✅ Yes ✅ Yes ✅ Yes APIs, getters/setters, main method
Class Anatomy — Access Levels at a Glance
class BankAccount
-balance : double
-pin : String
-owner : String
+getBalance() : double
+deposit(double) : void
+withdraw(double) : boolean
-validatePin(String) : boolean
-logTransaction(double) : void
+ = public  |  - = private  |  # = protected  |  ~ = default (package)
EX 2.1

Charon Access Modifiers — Complete Demo

Ek hi file mein charon modifiers ka behaviour dekhte hain — kya accessible hai, kya nahi aur compilation error kab aata hai.
// File: AccessDemo.java
class Vehicle {
    private   String engineSecret = "V8-Turbo";  // sirf Vehicle ke andar
              String model       = "Sedan";      // default = same package only
    protected int    year        = 2024;        // package + subclasses
    public    String brand       = "Toyota";   // everywhere

    private void    internalDiagnostics() { System.out.println("Internal Check OK"); }
    void            packageUtil()          { System.out.println("Package Util"); }
    protected void inheritableMethod()    { System.out.println("Inheritable"); }
    public void     describe()            {
        // Andar sab accessible hai!
        System.out.println("Engine: "  + engineSecret); // ✅ private — same class
        System.out.println("Model: "   + model);        // ✅ default — same class
        System.out.println("Year: "    + year);         // ✅ protected — same class
        System.out.println("Brand: "   + brand);        // ✅ public — same class
        internalDiagnostics();                             // ✅ private — same class
    }
}

// Subclass in SAME package
class ElectricCar extends Vehicle {
    void showDetails() {
        // System.out.println(engineSecret);    // ❌ COMPILE ERROR — private
        System.out.println("Model: "  + model);          // ✅ default — same pkg
        System.out.println("Year: "   + year);           // ✅ protected
        System.out.println("Brand: "  + brand);          // ✅ public
        inheritableMethod();                               // ✅ protected
        // internalDiagnostics();               // ❌ COMPILE ERROR — private
    }
}

public class AccessDemo {
    public static void main(String[] args) {
        Vehicle v = new Vehicle();
        v.describe();           // ✅ public method call

        System.out.println(v.model);  // ✅ default — same package
        System.out.println(v.year);   // ✅ protected — same package
        System.out.println(v.brand);  // ✅ public
        // System.out.println(v.engineSecret); // ❌ private — ERROR!

        ElectricCar ec = new ElectricCar();
        ec.showDetails();
    }
}
▶ Output
Engine: V8-Turbo Model: Sedan Year: 2024 Brand: Toyota Internal Check OK Model: Sedan Year: 2024 Brand: Toyota Inheritable Model: Sedan Year: 2024 Brand: Toyota
03

Getters & Setters

JavaBeans Convention · Naming Rules · Read-only · Write-only · Boolean Getter

📖 Complete Theory

Getters aur Setters — Controlled Access Door

Getter ek public method hai jo private field ki value return karta hai. Convention: get + FieldName() — jaise getName(), getBalance().

Setter ek public method hai jo private field ko set karta hai — with optional validation. Convention: set + FieldName(value) — jaise setName(String), setAge(int).

Boolean getter special convention: boolean type ke liye get ki jagah is use hota hai — jaise isActive(), isLoggedIn(). Ye JavaBeans specification ka part hai.

Flexibility principle: Ek field ke liye getter de do, setter nahi — read-only field. Ya setter do, getter nahi — write-only (rare, mostly passwords). Ya dono do — read-write. Ya kuch bhi nahi — completely hidden internal state. Ye choice tumhare paas hai!

Getter/Setter Flow — Private Field Access
CALLEROutside Code
Object.getX()
GETTERpublic method
returns value
PRIVATEprivate field
value: 42
CALLEROutside Code
Object.setX(v)
SETTERvalidates v
then assigns
PRIVATEprivate field
updated safely
EX 3.1

Student Class — Complete Getter/Setter Implementation

Sab types ke getters/setters — String, int, boolean, read-only, write-only — JavaBeans convention ke saath.
public class Student {

    // ── Private Fields — Direct access IMPOSSIBLE from outside ──
    private String  name;
    private int     age;
    private double  marks;
    private boolean isActive;
    private String  password;   // write-only — never returned!
    private final String rollNo;  // read-only after construction

    // ── Constructor ──
    public Student(String rollNo, String name, int age) {
        this.rollNo = rollNo;  // set once, never changeable (final)
        this.name   = name;
        this.age    = age;
        this.isActive = true;
    }

    // ── String Getter ──
    public String getName()  { return name; }

    // ── String Setter with Validation ──
    public void setName(String name) {
        if (name == null || name.trim().isEmpty())
            throw new IllegalArgumentException("Name empty nahi ho sakta!");
        if (name.length() > 50)
            throw new IllegalArgumentException("Name 50 chars se zyada nahi");
        this.name = name.trim();  // trim karo whitespace
    }

    // ── int Getter ──
    public int getAge() { return age; }

    // ── int Setter with Range Validation ──
    public void setAge(int age) {
        if (age < 5 || age > 25)
            throw new IllegalArgumentException("Age 5-25 ke beech hona chahiye! Got: " + age);
        this.age = age;
    }

    // ── double Getter ──
    public double getMarks() { return marks; }

    // ── double Setter ──
    public void setMarks(double marks) {
        if (marks < 0 || marks > 100)
            throw new IllegalArgumentException("Marks 0-100 ke beech hone chahiye!");
        this.marks = marks;
    }

    // ── Boolean Getter — NOTE: "is" prefix, not "get" ──
    public boolean isActive() { return isActive; }

    // ── Boolean Setter ──
    public void setActive(boolean active) { this.isActive = active; }

    // ── READ-ONLY: rollNo ka getter hai, setter NAHI ──
    public String getRollNo() { return rollNo; }
    // setRollNo() nahi hai — bahar se change impossible!

    // ── WRITE-ONLY: password ka setter hai, getter NAHI ──
    public void setPassword(String password) {
        if (password.length() < 8)
            throw new IllegalArgumentException("Password min 8 chars!");
        this.password = password;  // store (in real app: hash it!)
    }
    // getPassword() nahi hai — password kabhi expose nahi hoga!

    // ── Computed Getter (no field, derived value) ──
    public String getGrade() {
        if      (marks >= 90) return "A+";
        else if (marks >= 80) return "A";
        else if (marks >= 70) return "B";
        else if (marks >= 60) return "C";
        else if (marks >= 45) return "D";
        else                  return "F";
    }

    @Override
    public String toString() {
        return String.format("[%s] %s | Age: %d | Marks: %.1f | Grade: %s | Active: %s",
            rollNo, name, age, marks, getGrade(), isActive);
    }

    public static void main(String[] args) {
        Student s = new Student("CS001", "Rahul Sharma", 20);
        s.setMarks(87.5);
        s.setPassword("mypass123");
        System.out.println(s);

        // Read-only test
        System.out.println("Roll No: " + s.getRollNo());
        // s.setRollNo("XX999");  // ❌ COMPILE ERROR — method doesn't exist!

        // Boolean getter
        System.out.println("Is Active: " + s.isActive());
        s.setActive(false);
        System.out.println("After deactivate: " + s.isActive());

        // Validation test
        try {
            s.setAge(150);  // invalid!
        } catch (IllegalArgumentException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Computed getter
        System.out.println("Computed Grade: " + s.getGrade());
    }
}
▶ Output
[CS001] Rahul Sharma | Age: 20 | Marks: 87.5 | Grade: A | Active: true Roll No: CS001 Is Active: true After deactivate: false Caught: Age 5-25 ke beech hona chahiye! Got: 150 Computed Grade: A
04

Validation in Setters

Business Rules · Exception Handling · Custom Validators · Defensive Programming

📖 Theory — Why Validate in Setters?

Setter = First Line of Defense

Setter sirf assignment ke liye nahi hai — ye data integrity ki guarantee hai. Jab bhi koi field set ho, business rules satisfy hone chahiye. Ye ek single validated entry point hai.

Good setter mein hona chahiye:

  • Null Check: Reference types ke liye if (value == null) throw...
  • Range Check: Numbers ke liye min/max boundary enforce karo
  • Format Check: Email, phone, URL — regex ya dedicated validator
  • Business Rule: Domain-specific rules — age must be > 0, salary can't decrease by more than 50%
  • State Check: Agar object certain state mein ho to field change na ho
EX 4.1

BankAccount — Validation wali Complete Class

Real bank account jaise sab validations — deposit rules, withdrawal limits, minimum balance, PIN strength — sab setter/method level par.
public class BankAccount {

    private String accountNumber;
    private String owner;
    private double balance;
    private String pin;
    private boolean isLocked;
    private int    failedAttempts;

    private static final double MIN_BALANCE       = 500.0;
    private static final double MAX_DEPOSIT       = 100000.0;
    private static final int    MAX_FAILED_ATTEMPTS = 3;

    public BankAccount(String accNum, String owner, double initialBalance, String pin) {
        if (initialBalance < MIN_BALANCE)
            throw new IllegalArgumentException("Initial balance minimum Rs " + MIN_BALANCE + " hona chahiye!");
        this.accountNumber = accNum;
        this.owner         = owner;
        this.balance       = initialBalance;
        setPin(pin);  // validation ke saath!
    }

    // ── PIN Setter — strength validation ──
    public void setPin(String pin) {
        if (pin == null) throw new IllegalArgumentException("PIN null nahi ho sakta");
        if (!pin.matches("\\d{4,6}"))
            throw new IllegalArgumentException("PIN sirf 4-6 digits ka hona chahiye!");
        if (pin.equals("0000") || pin.equals("1234") || pin.equals("1111"))
            throw new IllegalArgumentException("PIN too simple! Use a strong PIN.");
        this.pin = pin;  // In real app: hash this!
    }

    // ── Deposit — amount validation ──
    public void deposit(double amount) {
        if (isLocked) throw new IllegalStateException("Account locked! Unlock karo pehle.");
        if (amount <= 0)           throw new IllegalArgumentException("Deposit amount positive hona chahiye!");
        if (amount > MAX_DEPOSIT)  throw new IllegalArgumentException("Single deposit max Rs " + MAX_DEPOSIT);
        this.balance += amount;
        System.out.println("✅ Deposited Rs " + amount + ". New balance: " + balance);
    }

    // ── Withdraw — multiple validations ──
    public void withdraw(double amount, String enteredPin) {
        if (isLocked) throw new IllegalStateException("Account locked!");
        if (!verifyPin(enteredPin)) return;  // pin verify internally
        if (amount <= 0)           throw new IllegalArgumentException("Withdrawal amount positive hona chahiye!");
        if (amount > balance)      throw new IllegalStateException("Insufficient balance!");
        if ((balance - amount) < MIN_BALANCE)
            throw new IllegalStateException("Minimum balance Rs " + MIN_BALANCE + " maintain karna zaroori hai!");
        this.balance -= amount;
        System.out.println("✅ Withdrawn Rs " + amount + ". New balance: " + balance);
    }

    // ── Private PIN verifier — internal logic hidden! ──
    private boolean verifyPin(String entered) {
        if (!pin.equals(entered)) {
            failedAttempts++;
            System.out.println("❌ Wrong PIN! Attempts left: " + (MAX_FAILED_ATTEMPTS - failedAttempts));
            if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
                isLocked = true;
                System.out.println("🔒 Account LOCKED after " + MAX_FAILED_ATTEMPTS + " failed attempts!");
            }
            return false;
        }
        failedAttempts = 0;  // reset on success
        return true;
    }

    public double  getBalance()       { return balance; }
    public String  getOwner()         { return owner; }
    public String  getAccountNumber() { return accountNumber; }
    public boolean isLocked()         { return isLocked; }
    // getPin() — NEVER! Password/PIN ke liye getter mat banao

    public static void main(String[] args) {
        BankAccount acc = new BankAccount("ACC123", "Rahul", 5000.0, "9876");
        System.out.println("Owner: " + acc.getOwner() + ", Balance: " + acc.getBalance());

        acc.deposit(2000);
        acc.withdraw(500, "9876");

        // Wrong PIN attempts
        acc.withdraw(100, "0000");
        acc.withdraw(100, "1111");
        acc.withdraw(100, "2222");   // 3rd wrong → lock!

        try {
            acc.deposit(100);           // locked!
        } catch (IllegalStateException e) {
            System.out.println("Error: " + e.getMessage());
        }

        try {
            BankAccount weak = new BankAccount("ACC456", "Test", 1000, "1234");
        } catch (IllegalArgumentException e) {
            System.out.println("PIN Error: " + e.getMessage());
        }
    }
}
▶ Output
Owner: Rahul, Balance: 5000.0 ✅ Deposited Rs 2000.0. New balance: 7000.0 ✅ Withdrawn Rs 500.0. New balance: 6500.0 ❌ Wrong PIN! Attempts left: 2 ❌ Wrong PIN! Attempts left: 1 ❌ Wrong PIN! Attempts left: 0 🔒 Account LOCKED after 3 failed attempts! Error: Account locked! Unlock karo pehle. PIN Error: PIN too simple! Use a strong PIN.
05

Constructors & Encapsulation

Parameterized Constructor · Constructor Overloading · this() Chaining · Mandatory Fields

📖 Theory — Constructors Ka Role

Constructor = Object Ki Birth Certificate

Constructor encapsulation ka extension hai — ye ensure karta hai ki object birth se hi valid state mein ho. Agar fields mandatory hain, constructor mein force karo — default constructor mat dena.

Private Constructor: Singleton pattern mein use hota hai — bahar se new impossible! Factory method se instance milta hai.

Constructor Chaining (this()): Ek constructor dusre ko call kare — code duplication avoid hoti hai. this() hamesha pehli line honi chahiye.

EX 5.1

Employee Class — Constructor Chaining + Encapsulation

Constructor overloading, chaining, mandatory field enforcement aur Singleton pattern ek saath.
public class Employee {

    private final String empId;       // mandatory — no setter
    private       String name;
    private       String department;
    private       double salary;
    private       int    experienceYears;

    // ── Constructor 1: Most detailed (base constructor) ──
    public Employee(String empId, String name, String dept, double salary, int exp) {
        if (empId == null || empId.trim().isEmpty())
            throw new IllegalArgumentException("Employee ID mandatory hai!");
        if (name == null || name.trim().isEmpty())
            throw new IllegalArgumentException("Name mandatory hai!");
        if (salary < 0)
            throw new IllegalArgumentException("Salary negative nahi ho sakti!");
        this.empId          = empId.toUpperCase();
        this.name           = name.trim();
        this.department     = dept != null ? dept : "General";
        this.salary         = salary;
        this.experienceYears = exp;
    }

    // ── Constructor 2: Without experience (chains to Constructor 1) ──
    public Employee(String empId, String name, String dept, double salary) {
        this(empId, name, dept, salary, 0);  // this() — pehli line MUST
    }

    // ── Constructor 3: Minimum — id + name only ──
    public Employee(String empId, String name) {
        this(empId, name, "General", 30000.0, 0);  // defaults for rest
    }

    // ── Getters ──
    public String getEmpId()           { return empId; }
    public String getName()            { return name; }
    public String getDepartment()      { return department; }
    public double getSalary()          { return salary; }
    public int    getExperienceYears() { return experienceYears; }

    // ── Setters with validation ──
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) throw new IllegalArgumentException("Name blank nahi ho sakta");
        this.name = name.trim();
    }
    public void setDepartment(String dept) { this.department = dept; }
    public void setSalary(double salary) {
        if (salary < 0) throw new IllegalArgumentException("Salary negative nahi ho sakti!");
        if (salary < this.salary * 0.5) throw new IllegalArgumentException("Salary 50% se zyada reduce nahi ho sakti!");
        this.salary = salary;
    }

    // ── Business Method ──
    public void promote(double percentageHike) {
        if (percentageHike <= 0 || percentageHike > 100)
            throw new IllegalArgumentException("Hike 0-100% ke beech hona chahiye!");
        double newSalary = salary * (1 + percentageHike / 100);
        System.out.printf("🎉 %s promoted! Salary: %.0f → %.0f (%.0f%% hike)%n",
            name, salary, newSalary, percentageHike);
        this.salary = newSalary;
        this.experienceYears++;
    }

    @Override
    public String toString() {
        return String.format("Employee{id='%s', name='%s', dept='%s', salary=%.0f, exp=%d yrs}",
            empId, name, department, salary, experienceYears);
    }

    public static void main(String[] args) {
        // Different constructors
        Employee e1 = new Employee("EMP001", "Rahul", "Engineering", 75000, 3);
        Employee e2 = new Employee("EMP002", "Priya", "Marketing", 60000);
        Employee e3 = new Employee("EMP003", "Amit");  // minimum

        System.out.println(e1);
        System.out.println(e2);
        System.out.println(e3);

        e1.promote(15);
        System.out.println(e1);

        // Salary reduction >50% reject
        try {
            e2.setSalary(10000);  // 60000 → 10000 = too much drop
        } catch (IllegalArgumentException ex) {
            System.out.println("Salary Error: " + ex.getMessage());
        }
    }
}
▶ Output
Employee{id='EMP001', name='Rahul', dept='Engineering', salary=75000, exp=3 yrs} Employee{id='EMP002', name='Priya', dept='Marketing', salary=60000, exp=0 yrs} Employee{id='EMP003', name='Amit', dept='General', salary=30000, exp=0 yrs} 🎉 Rahul promoted! Salary: 75000 → 86250 (15% hike) Employee{id='EMP001', name='Rahul', dept='Engineering', salary=86250, exp=4 yrs} Salary Error: Salary 50% se zyada reduce nahi ho sakti!
06

The this Keyword

Disambiguation · Method Chaining · this() Constructor Call · Pass Current Object

📖 Theory — this Keyword ke 4 Uses

this — Current Object Ka Reference

this keyword current class ke instance ka reference hai. Encapsulation ke context mein ye mostly getter/setter mein use hota hai jab local variable (parameter) aur instance variable ka naam same ho.

  • Use 1 — Ambiguity Resolve: this.name = name — left side instance variable, right side parameter
  • Use 2 — Constructor Chaining: this(args) — ek constructor se dusra call karo
  • Use 3 — Method Chaining (Builder): return this — method khud return kare for chaining
  • Use 4 — Pass Current Object: someMethod(this) — apne aap ko argument ke roop mein dena
EX 6.1

this Keyword — Charon Uses Ek Saath

Ambiguity resolution, method chaining (Fluent API/Builder style), aur current object passing — sab dekhte hain.
public class UserProfile {

    private String  username;
    private String  email;
    private int     age;
    private String  city;
    private boolean newsletter;

    // ── USE 1: Ambiguity resolve — parameter naam same hai field se ──
    public void setUsername(String username) {   // param: username
        this.username = username;  // this.username = instance field
        // Bina this: "username = username" kuch nahi karega (local = local)
    }

    public void setEmail(String email) {
        if (!email.contains("@")) throw new IllegalArgumentException("Invalid email!");
        this.email = email.toLowerCase();
    }

    // ── USE 3: Method Chaining — return this! ──
    // Har setter this return karta hai → ek ke baad ek call kar sako
    public UserProfile withAge(int age) {
        if (age < 13 || age > 120) throw new IllegalArgumentException("Invalid age!");
        this.age = age;
        return this;  // ← KEY — current object return karo
    }

    public UserProfile inCity(String city) {
        this.city = city;
        return this;
    }

    public UserProfile subscribeNewsletter(boolean subscribe) {
        this.newsletter = subscribe;
        return this;
    }

    public UserProfile withEmail(String email) {
        setEmail(email);  // reuse existing setter with validation
        return this;
    }

    // ── USE 4: Pass current object to another method ──
    public void registerWith(UserRegistry registry) {
        registry.register(this);  // apne aap ko registry ko dedo
    }

    public String getUsername()  { return username; }
    public String getEmail()     { return email; }
    public int    getAge()       { return age; }
    public String getCity()      { return city; }
    public boolean isNewsletter(){ return newsletter; }

    @Override
    public String toString() {
        return String.format(
            "UserProfile{user='%s', email='%s', age=%d, city='%s', newsletter=%s}",
            username, email, age, city, newsletter);
    }

    public static void main(String[] args) {
        // Method chaining — ek line mein sab set karo!
        UserProfile user = new UserProfile();
        user.setUsername("rahul_dev");

        // Chained calls — clean, readable!
        user.withEmail("rahul@example.com")
            .withAge(25)
            .inCity("Indore")
            .subscribeNewsletter(true);

        System.out.println(user);

        // Another user — one-liner!
        UserProfile admin = new UserProfile();
        admin.setUsername("admin");
        admin.withEmail("admin@company.com").withAge(30).inCity("Mumbai").subscribeNewsletter(false);
        System.out.println(admin);
    }
}

// Helper class for USE 4 demo
class UserRegistry {
    public void register(UserProfile user) {
        System.out.println("Registered: " + user.getUsername() + " from " + user.getCity());
    }
}
▶ Output
UserProfile{user='rahul_dev', email='rahul@example.com', age=25, city='Indore', newsletter=true} UserProfile{user='admin', email='admin@company.com', age=30, city='Mumbai', newsletter=false}
07

Immutable Classes

final Fields · No Setters · Deep Immutability · Thread Safety · Value Objects

📖 Theory — Immutability Kya Hai?

Immutable Object — Ek Baar Bana, Kabhi Na Bada

Ek immutable class ka object create hone ke baad uski state kabhi bhi change nahi hoti. Java ka String, Integer, LocalDate — sab immutable hain.

Immutable class banane ke 5 rules:

  • Class ko final banao — subclassing prevent hoti hai
  • Sab fields private final karo
  • Koi setter nahi — fields constructor mein ek baar set hoti hain
  • Mutable objects (like arrays, lists) ka deep copy karo constructor mein aur getter mein
  • No method jo internal state modify kare

Benefits: Thread-safe by default (no synchronization needed!), safe for caching, HashMap keys ke liye perfect, reasoning easy hai.

EX 7.1

Immutable Money Class — Perfect Value Object

Bank mein currency represent karne wali immutable class — ek baar amount aur currency set hoti hai, kabhi change nahi hoti. Operations naya object return karte hain.
public final class Money {          // final — subclassing prevent

    private final double amount;    // final — ek baar set, never change
    private final String currency;

    public Money(double amount, String currency) {
        if (amount < 0) throw new IllegalArgumentException("Amount negative nahi ho sakta!");
        if (currency == null || currency.trim().isEmpty())
            throw new IllegalArgumentException("Currency code required!");
        this.amount   = amount;
        this.currency = currency.toUpperCase();
    }

    // ── Getters only — NO setters! ──
    public double getAmount()   { return amount; }
    public String getCurrency() { return currency; }

    // ── Operations return NEW Money object — original unchanged! ──
    public Money add(Money other) {
        if (!currency.equals(other.currency))
            throw new IllegalArgumentException("Alag currencies add nahi kar sakte!");
        return new Money(amount + other.amount, currency);  // NEW object!
    }

    public Money subtract(Money other) {
        if (!currency.equals(other.currency)) throw new IllegalArgumentException("Currency mismatch!");
        if (amount < other.amount)            throw new IllegalArgumentException("Insufficient!");
        return new Money(amount - other.amount, currency);
    }

    public Money multiply(double factor) {
        if (factor < 0) throw new IllegalArgumentException("Factor negative nahi ho sakta!");
        return new Money(amount * factor, currency);
    }

    public boolean isGreaterThan(Money other) { return this.amount > other.amount; }
    public boolean isZero()                     { return amount == 0; }

    @Override
    public String toString() {
        return String.format("%s %.2f", currency, amount);
    }

    public static void main(String[] args) {
        Money price   = new Money(1000.0, "INR");
        Money tax     = new Money(180.0,  "INR");
        Money discount= new Money(50.0,   "INR");

        System.out.println("Price:       " + price);
        System.out.println("Tax (18%):   " + tax);
        System.out.println("Discount:    " + discount);

        Money total = price.add(tax).subtract(discount);  // chaining!
        System.out.println("Total:       " + total);
        System.out.println("Price unchanged: " + price);  // original safe!

        Money doubled = price.multiply(2);
        System.out.println("Doubled price:   " + doubled);

        // Currency mismatch test
        try {
            Money usd = new Money(10, "USD");
            price.add(usd);  // different currencies!
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
▶ Output
Price: INR 1000.00 Tax (18%): INR 180.00 Discount: INR 50.00 Total: INR 1130.00 Price unchanged: INR 1000.00 Doubled price: INR 2000.00 Error: Alag currencies add nahi kar sakte!
08

Real-World Examples

Library System · E-Commerce Product · Hospital Patient · Builder Pattern

EX 8.1

Library Book System — Complete Encapsulated

Library mein book issue/return karna — availability tracking, fine calculation, member validation — sab encapsulated.
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class LibraryBook {

    private final  String    isbn;
    private final  String    title;
    private final  String    author;
    private        boolean   isAvailable;
    private        String    borrowedBy;
    private        LocalDate issueDate;
    private        int       totalIssues;
    private static final int MAX_DAYS   = 14;    // 2 weeks
    private static final double FINE_PER_DAY = 5.0;

    public LibraryBook(String isbn, String title, String author) {
        if (isbn == null || title == null || author == null)
            throw new IllegalArgumentException("ISBN, title, author all mandatory!");
        this.isbn        = isbn;
        this.title       = title;
        this.author      = author;
        this.isAvailable = true;
    }

    public void issueBook(String memberName) {
        if (!isAvailable)
            throw new IllegalStateException("Book already issued to: " + borrowedBy);
        if (memberName == null || memberName.trim().isEmpty())
            throw new IllegalArgumentException("Member name required!");

        this.borrowedBy  = memberName;
        this.issueDate   = LocalDate.now();
        this.isAvailable = false;
        this.totalIssues++;
        System.out.printf("📖 '%s' issued to %s on %s. Due: %s%n",
            title, memberName, issueDate, issueDate.plusDays(MAX_DAYS));
    }

    public void returnBook() {
        if (isAvailable) throw new IllegalStateException("Book pehle se available hai!");
        long   daysHeld    = ChronoUnit.DAYS.between(issueDate, LocalDate.now());
        long   overdue     = Math.max(0, daysHeld - MAX_DAYS);
        double fine        = overdue * FINE_PER_DAY;

        System.out.printf("✅ '%s' returned by %s. Days held: %d", title, borrowedBy, daysHeld);
        if (fine > 0) System.out.printf(", Overdue: %d days, Fine: Rs %.0f", overdue, fine);
        System.out.println();

        this.isAvailable = true;
        this.borrowedBy  = null;
        this.issueDate   = null;
    }

    public String  getIsbn()        { return isbn; }
    public String  getTitle()       { return title; }
    public String  getAuthor()      { return author; }
    public boolean isAvailable()    { return isAvailable; }
    public int     getTotalIssues() { return totalIssues; }
    public String  getBorrowedBy()  { return isAvailable ? "Available" : borrowedBy; }

    public static void main(String[] args) {
        LibraryBook book = new LibraryBook("978-0134685991", "Effective Java", "Joshua Bloch");
        System.out.println("Available: " + book.isAvailable());

        book.issueBook("Rahul Sharma");
        System.out.println("Borrowed By: " + book.getBorrowedBy());

        try {
            book.issueBook("Priya");   // already issued!
        } catch (IllegalStateException e) {
            System.out.println("Issue Error: " + e.getMessage());
        }

        book.returnBook();
        System.out.println("Total issues: " + book.getTotalIssues());
        book.issueBook("Priya Patel");   // now possible
    }
}
▶ Output
Available: true 📖 'Effective Java' issued to Rahul Sharma on 2025-05-13. Due: 2025-05-27 Borrowed By: Rahul Sharma Issue Error: Book already issued to: Rahul Sharma ✅ 'Effective Java' returned by Rahul Sharma. Days held: 0 Total issues: 1 📖 'Effective Java' issued to Priya Patel on 2025-05-13. Due: 2025-05-27
09

Best Practices

DOs and DON'Ts · SOLID · Common Mistakes · Interview Tips

✅ Encapsulation Best Practices

  • Sab instance variables hamesha private rakho — default rule
  • Validate karo setter mein — trust no one outside the class
  • Immutable objects prefer karo jab state change na ho
  • Mutable objects ka getter defensive copy return kare
  • toString() override karo debugging ke liye
  • Boolean fields ke liye is prefix convention follow karo
  • Private helper methods freely use karo — internal logic split karo
  • Constants private static final karo

❌ Common Mistakes to Avoid

  • Password/PIN ke liye getter banana — kabhi nahi!
  • All-public fields with trivial getters (no validation) — useless encapsulation
  • Mutable object directly return karna from getter
  • Business logic bahar rakhna (outside the class)
  • Too many setters — maybe you need immutability
  • Default constructor chhod dena jab mandatory fields hain
  • public static mutable fields — worst practice
  • Getter se internal array direct return karna (array modification possible)
Mutable Object Getter Trap: public int[] getScores() { return scores; } — caller array modify kar sakta hai directly! Fix: return Arrays.copyOf(scores, scores.length); — defensive copy return karo.
Interview Tip: Jab encapsulation poochha jaye, hamesha teen points bolo: (1) private fields, (2) public getters/setters with validation, (3) benefits: data security, flexibility, maintainability. Code example mein BankAccount ya Student class dena best hai.
🎯
SOLID Connection: Encapsulation ka seedha connection Single Responsibility Principle (SRP) se hai — ek class sirf apna data manage kare. Aur Open/Closed Principle (OCP) se — internal implementation change karo without breaking external code.
💡
Lombok shortcut (Production): Spring Boot mein @Getter @Setter @NoArgsConstructor @AllArgsConstructor annotations se automatically getters/setters generate hote hain. Ye Lombok library hai. Lekin validation ke liye phir bhi custom setter likhna padta hai.
10

Practice Programs

Topic-wise 4-5 Problems · Click to See Solution · Beginner to Advanced

📝

Set A — Basic Encapsulation

Private fields, getters, setters, toString — fundamentals solid karo

1
Rectangle Class — Area, Perimeter, Validation Length aur width private rakho. Negative values reject karo. Area, Perimeter compute karo. toString() override karo.
Getters/SettersValidation
private double length, width — validate > 0 in setters — computed getters for area/perimeter
public class Rectangle {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        setLength(length);
        setWidth(width);
    }

    public double getLength() { return length; }
    public double getWidth()  { return width; }

    public void setLength(double length) {
        if (length <= 0) throw new IllegalArgumentException("Length positive hona chahiye! Got: " + length);
        this.length = length;
    }

    public void setWidth(double width) {
        if (width <= 0) throw new IllegalArgumentException("Width positive hona chahiye! Got: " + width);
        this.width = width;
    }

    // Computed — no separate fields needed
    public double getArea()      { return length * width; }
    public double getPerimeter() { return 2 * (length + width); }
    public boolean isSquare()   { return length == width; }

    @Override
    public String toString() {
        return String.format(
            "Rectangle[%.1f × %.1f] | Area=%.2f | Perimeter=%.2f | Square=%s",
            length, width, getArea(), getPerimeter(), isSquare());
    }

    public static void main(String[] args) {
        Rectangle r1 = new Rectangle(5, 3);
        Rectangle r2 = new Rectangle(4, 4);
        System.out.println(r1);
        System.out.println(r2);
        r1.setLength(10);
        System.out.println("After resize: " + r1);
        try {
            new Rectangle(-2, 5);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
▶ Output
Rectangle[5.0 × 3.0] | Area=15.00 | Perimeter=16.00 | Square=false Rectangle[4.0 × 4.0] | Area=16.00 | Perimeter=16.00 | Square=true After resize: Rectangle[10.0 × 3.0] | Area=30.00 | Perimeter=26.00 | Square=false Error: Length positive hona chahiye! Got: -2.0
2
Person Class — Age Validation + Name Formatting Name (title-case auto-format), age (1-120 range), email (@ check) — sab private with smart setters.
Auto-FormatValidation
Name title-case convert, age range check, email regex — smart setter transformations
public class Person {
    private String name;
    private int    age;
    private String email;

    public Person(String name, int age, String email) {
        setName(name); setAge(age); setEmail(email);
    }

    public String getName()  { return name; }
    public int    getAge()   { return age; }
    public String getEmail() { return email; }

    public void setName(String name) {
        if (name == null || name.trim().isEmpty())
            throw new IllegalArgumentException("Name required!");
        // Auto title-case: "rahul sharma" → "Rahul Sharma"
        String[] parts = name.trim().split("\\s+");
        StringBuilder sb = new StringBuilder();
        for (String p : parts) {
            if (!p.isEmpty()) sb.append(Character.toUpperCase(p.charAt(0)))
                              .append(p.substring(1).toLowerCase()).append(" ");
        }
        this.name = sb.toString().trim();
    }

    public void setAge(int age) {
        if (age < 1 || age > 120)
            throw new IllegalArgumentException("Age 1-120 ke beech hona chahiye!");
        this.age = age;
    }

    public void setEmail(String email) {
        if (email == null || !email.contains("@") || !email.contains("."))
            throw new IllegalArgumentException("Valid email required (must have @ and .)");
        this.email = email.toLowerCase().trim();
    }

    public String getCategory() {
        if      (age < 13) return "Child";
        else if (age < 18) return "Teenager";
        else if (age < 60) return "Adult";
        else               return "Senior";
    }

    @Override
    public String toString() {
        return String.format("Person{name='%s', age=%d (%s), email='%s'}",
            name, age, getCategory(), email);
    }

    public static void main(String[] args) {
        Person p = new Person("rahul SHARMA", 25, "RAHUL@Gmail.Com");
        System.out.println(p);  // name auto-formatted, email lowercase

        try { new Person("Test", 200, "test@a.com"); }
        catch (IllegalArgumentException e) { System.out.println("Age Error: "+e.getMessage()); }

        try { new Person("Test", 20, "invalidemail"); }
        catch (IllegalArgumentException e) { System.out.println("Email Error: "+e.getMessage()); }
    }
}
▶ Output
Person{name='Rahul Sharma', age=25 (Adult), email='rahul@gmail.com'} Age Error: Age 1-120 ke beech hona chahiye! Email Error: Valid email required (must have @ and .)
3
Counter Class — Thread-Safe Encapsulated Counter Ek counter banao — increment, decrement, reset, min/max enforce karo. Negative counter prevent karo.
BoundaryState Management
private count, min=0 max enforce — increment/decrement boundary check
public class Counter {
    private int       count;
    private final int minValue;
    private final int maxValue;
    private int       totalIncrements;
    private int       totalDecrements;

    public Counter(int initial, int min, int max) {
        if (min >= max)      throw new IllegalArgumentException("min must be < max!");
        if (initial < min || initial > max)
            throw new IllegalArgumentException("Initial must be in [min, max]");
        this.count = initial; this.minValue = min; this.maxValue = max;
    }

    public Counter() { this(0, 0, Integer.MAX_VALUE); }  // default

    public boolean increment() {
        if (count >= maxValue) { System.out.println("Max ("+maxValue+") reached!"); return false; }
        count++; totalIncrements++; return true;
    }

    public boolean decrement() {
        if (count <= minValue) { System.out.println("Min ("+minValue+") reached!"); return false; }
        count--; totalDecrements++; return true;
    }

    public void incrementBy(int n) { for (int i=0; iif(!increment()) break; }
    public void reset()            { count = minValue; }

    public int    getCount()           { return count; }
    public int    getTotalIncrements() { return totalIncrements; }
    public int    getTotalDecrements() { return totalDecrements; }
    public boolean isAtMax()          { return count == maxValue; }
    public boolean isAtMin()          { return count == minValue; }

    @Override
    public String toString() {
        return String.format("Counter{count=%d, range=[%d,%d], +%d, -%d}",
            count, minValue, maxValue, totalIncrements, totalDecrements);
    }

    public static void main(String[] args) {
        Counter c = new Counter(0, 0, 5);
        c.incrementBy(7);       // only goes to 5
        System.out.println(c);
        c.decrement(); c.decrement();
        System.out.println(c);
        c.reset();
        System.out.println("After reset: " + c.getCount());
        for (int i=0; i<3; i++) c.decrement(); // only 1 works
    }
}
▶ Output
Max (5) reached! Max (5) reached! Counter{count=5, range=[0,5], +5, -0} Counter{count=3, range=[0,5], +5, -2} After reset: 0 Min (0) reached! Min (0) reached!
4
Circle Class — Radius Validation + Comparator Circle ka radius private rakho. Area, circumference compute karo. Do circles compare karo (area ke basis pe).
Computed GettersComparison
private double radius — π constant — computed area/circumference — compare method
public class Circle {
    private double radius;
    private static final double PI = Math.PI;

    public Circle(double radius) { setRadius(radius); }

    public double getRadius() { return radius; }
    public void   setRadius(double radius) {
        if (radius <= 0) throw new IllegalArgumentException("Radius must be positive!");
        this.radius = radius;
    }

    public double getArea()          { return PI * radius * radius; }
    public double getCircumference() { return 2 * PI * radius; }
    public double getDiameter()      { return 2 * radius; }

    public boolean isLargerThan(Circle other) { return this.getArea() > other.getArea(); }
    public boolean canFitInside(Circle other)  { return this.radius < other.radius; }
    public boolean intersectsWith(Circle other, double distBetweenCenters) {
        return distBetweenCenters < (this.radius + other.radius);
    }

    @Override
    public String toString() {
        return String.format("Circle[r=%.1f | area=%.2f | circumf=%.2f]",
            radius, getArea(), getCircumference());
    }

    public static void main(String[] args) {
        Circle c1 = new Circle(5.0);
        Circle c2 = new Circle(3.0);
        System.out.println(c1);
        System.out.println(c2);
        System.out.println("c1 larger than c2: " + c1.isLargerThan(c2));
        System.out.println("c2 fits in c1:     " + c2.canFitInside(c1));
        System.out.println("Intersect (d=6):   " + c1.intersectsWith(c2, 6));
    }
}
▶ Output
Circle[r=5.0 | area=78.54 | circumf=31.42] Circle[r=3.0 | area=28.27 | circumf=18.85] c1 larger than c2: true c2 fits in c1: true Intersect (d=6): true
🏦

Set B — Real-World Encapsulation

Domain-driven classes with complete business logic

5
Product Inventory — Stock Management E-commerce product class — price, stock quantity, discount apply, out-of-stock check, restock.
E-CommerceBusiness Logic
private price, stock — buy() reduces stock — restock() adds — applyDiscount() validates %
public class Product {
    private final String  productId;
    private       String  name;
    private       double  price;
    private       int     stock;
    private       double  discountPercent;
    private       int     totalSold;

    public Product(String id, String name, double price, int stock) {
        this.productId = id;
        setName(name); setPrice(price); restock(stock);
    }

    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) throw new IllegalArgumentException("Name required!");
        this.name = name.trim();
    }
    public void setPrice(double price) {
        if (price < 0) throw new IllegalArgumentException("Price negative nahi ho sakti!");
        this.price = price;
    }

    public void applyDiscount(double percent) {
        if (percent < 0 || percent > 90) throw new IllegalArgumentException("Discount 0-90% ke beech!");
        this.discountPercent = percent;
        System.out.printf("💰 %s pe %.0f%% discount applied! Effective price: Rs %.2f%n",
            name, percent, getEffectivePrice());
    }

    public double getEffectivePrice() { return price * (1 - discountPercent / 100); }

    public void restock(int qty) {
        if (qty <= 0) throw new IllegalArgumentException("Restock qty positive hona chahiye!");
        stock += qty;
        System.out.println("📦 " + name + ": Restocked " + qty + ". Total stock: " + stock);
    }

    public boolean buy(int qty) {
        if (qty <= 0)    { System.out.println("Invalid qty!"); return false; }
        if (stock < qty) { System.out.println("❌ Insufficient stock! Available: "+stock); return false; }
        stock     -= qty;
        totalSold += qty;
        System.out.printf("🛒 Bought %d x %s @ Rs %.2f each. Total: Rs %.2f%n",
            qty, name, getEffectivePrice(), qty * getEffectivePrice());
        if (stock < 5) System.out.println("⚠️ Low stock alert! Only " + stock + " left.");
        return true;
    }

    public String  getProductId() { return productId; }
    public String  getName()      { return name; }
    public double getPrice()      { return price; }
    public int    getStock()      { return stock; }
    public int    getTotalSold()  { return totalSold; }
    public boolean isOutOfStock() { return stock == 0; }

    @Override
    public String toString() {
        return String.format("[%s] %s | MRP:Rs%.0f | Eff:Rs%.2f | Stock:%d | Sold:%d",
            productId, name, price, getEffectivePrice(), stock, totalSold);
    }

    public static void main(String[] args) {
        Product p = new Product("PROD001", "Laptop", 55000, 10);
        System.out.println(p);
        p.applyDiscount(15);
        p.buy(3);
        p.buy(5);
        p.buy(4);  // insufficient
        System.out.println(p);
    }
}
▶ Output
📦 Laptop: Restocked 10. Total stock: 10 [PROD001] Laptop | MRP:Rs55000 | Eff:Rs55000.00 | Stock:10 | Sold:0 💰 Laptop pe 15% discount applied! Effective price: Rs 46750.00 🛒 Bought 3 x Laptop @ Rs 46750.00 each. Total: Rs 140250.00 🛒 Bought 5 x Laptop @ Rs 46750.00 each. Total: Rs 233750.00 ⚠️ Low stock alert! Only 2 left. ❌ Insufficient stock! Available: 2 [PROD001] Laptop | MRP:Rs55000 | Eff:Rs46750.00 | Stock:2 | Sold:8
6
Car — Fuel, Speed, Gear Encapsulation Car class — max speed limit, fuel level check, gear validation, acceleration only when enough fuel.
State MachineDomain Logic
private speed, fuel, gear — accelerate checks fuel — refuel validates — gear range 1-6
public class Car {
    private final String  brand;
    private final String  model;
    private       double  fuelLevel;       // liters
    private       int     speed;           // km/h
    private       int     gear;            // 1-6
    private       boolean engineOn;

    private static final double MAX_FUEL       = 50.0;
    private static final int    MAX_SPEED      = 200;
    private static final double FUEL_PER_KM   = 0.08;

    public Car(String brand, String model) {
        this.brand = brand; this.model = model;
        this.fuelLevel = MAX_FUEL; this.gear = 1;
    }

    public void startEngine() {
        if (fuelLevel <= 0) { System.out.println("⛽ No fuel! Cannot start."); return; }
        engineOn = true; System.out.println("🔑 Engine started! Vroom...");
    }

    public void accelerate(int amount) {
        if (!engineOn)         { System.out.println("Start engine first!"); return; }
        if (fuelLevel <= 1.0)  { System.out.println("⚠️ Low fuel! Speed maintained."); return; }
        if (amount <= 0) throw new IllegalArgumentException("Acceleration positive hona chahiye!");
        speed = Math.min(speed + amount, MAX_SPEED);
        fuelLevel -= amount * FUEL_PER_KM * 0.5;
        fuelLevel = Math.max(0, fuelLevel);
        System.out.printf("🚗 Speed: %d km/h | Fuel: %.1f L | Gear: %d%n", speed, fuelLevel, gear);
    }

    public void brake(int amount) {
        speed = Math.max(0, speed - amount);
        if (speed == 0) { engineOn = false; System.out.println("🛑 Stopped."); }
        else System.out.println("Speed reduced to: " + speed + " km/h");
    }

    public void shiftGear(int newGear) {
        if (newGear < 1 || newGear > 6) throw new IllegalArgumentException("Gear 1-6 ke beech!");
        if (Math.abs(newGear - gear) > 1) { System.out.println("⚠️ Skip gear not recommended!"); }
        gear = newGear; System.out.println("Gear shifted to: " + gear);
    }

    public void refuel(double liters) {
        if (liters <= 0) throw new IllegalArgumentException("Refuel amount positive hona chahiye!");
        fuelLevel = Math.min(fuelLevel + liters, MAX_FUEL);
        System.out.printf("⛽ Refueled. Fuel: %.1f/%.1f L%n", fuelLevel, MAX_FUEL);
    }

    public String  getBrand()     { return brand; }
    public String  getModel()     { return model; }
    public int    getSpeed()     { return speed; }
    public double getFuelLevel() { return fuelLevel; }
    public int    getGear()      { return gear; }
    public boolean isEngineOn() { return engineOn; }

    public static void main(String[] args) {
        Car car = new Car("Maruti", "Swift");
        car.startEngine();
        car.accelerate(40);
        car.shiftGear(2);
        car.accelerate(60);
        car.shiftGear(3);
        car.accelerate(80);
        car.brake(50);
        car.brake(130);
    }
}
▶ Output
🔑 Engine started! Vroom... 🚗 Speed: 40 km/h | Fuel: 48.4 L | Gear: 1 Gear shifted to: 2 🚗 Speed: 100 km/h | Fuel: 45.8 L | Gear: 2 Gear shifted to: 3 🚗 Speed: 180 km/h | Fuel: 42.6 L | Gear: 3 Speed reduced to: 130 km/h 🛑 Stopped.
7
Temperature — Celsius/Fahrenheit/Kelvin Converter Temperature internally Celsius mein store karo. Getters for all 3 formats. Absolute zero prevent karo. Unit-agnostic setter.
ConversionImmutable Storage
Internal celsius — computed fahrenheit/kelvin getters — factory methods for each unit
public class Temperature {
    private double celsius;  // internal storage always Celsius
    private static final double ABSOLUTE_ZERO_C = -273.15;

    private Temperature(double celsius) { setCelsius(celsius); }

    // Factory Methods — create from any unit
    public static Temperature fromCelsius(double c)    { return new Temperature(c); }
    public static Temperature fromFahrenheit(double f) { return new Temperature((f - 32) * 5 / 9); }
    public static Temperature fromKelvin(double k)     { return new Temperature(k + ABSOLUTE_ZERO_C); }

    public void setCelsius(double celsius) {
        if (celsius < ABSOLUTE_ZERO_C)
            throw new IllegalArgumentException("Absolute zero ("+ABSOLUTE_ZERO_C+"°C) se neeche nahi ja sakte!");
        this.celsius = celsius;
    }

    // Getters in all 3 formats
    public double getCelsius()    { return celsius; }
    public double getFahrenheit() { return celsius * 9 / 5 + 32; }
    public double getKelvin()     { return celsius - ABSOLUTE_ZERO_C; }

    public String getState() {
        if      (celsius <= 0)   return "Ice ❄️";
        else if (celsius < 100) return "Liquid 💧";
        else                    return "Steam 💨";
    }

    @Override
    public String toString() {
        return String.format("%.2f°C = %.2f°F = %.2fK [%s]",
            celsius, getFahrenheit(), getKelvin(), getState());
    }

    public static void main(String[] args) {
        System.out.println(Temperature.fromCelsius(100));
        System.out.println(Temperature.fromCelsius(0));
        System.out.println(Temperature.fromFahrenheit(98.6));  // body temp
        System.out.println(Temperature.fromKelvin(0));         // absolute zero

        try {
            Temperature.fromCelsius(-300);  // below absolute zero!
        } catch (IllegalArgumentException e) { System.out.println("Error: " + e.getMessage()); }
    }
}
▶ Output
100.00°C = 212.00°F = 373.15K [Steam 💨] 0.00°C = 32.00°F = 273.15K [Ice ❄️] 37.00°C = 98.60°F = 310.15K [Liquid 💧] -273.15°C = -459.67°F = 0.00K [Ice ❄️] Error: Absolute zero (-273.15°C) se neeche nahi ja sakte!
🎯

Set C — Advanced Encapsulation Patterns

Immutability, Builder, Singleton — interview-level problems

8
Singleton Pattern — Configuration Manager Ek hi instance ho poore application mein config ke liye. Private constructor, getInstance() se milta hai.
SingletonDesign Pattern
private static instance — private constructor — public getInstance() — lazy init
import java.util.HashMap;
import java.util.Map;

public class AppConfig {
    // Single instance — private static
    private static AppConfig instance;

    // Private fields
    private final Map<String, String> configs;
    private String environment;
    private boolean debugMode;

    // PRIVATE constructor — new AppConfig() bahar se IMPOSSIBLE!
    private AppConfig() {
        configs     = new HashMap<>();
        environment = "development";
        debugMode   = false;
        loadDefaults();
        System.out.println("⚙️ AppConfig initialized (only once!)");
    }

    private void loadDefaults() {
        configs.put("db.host",      "localhost");
        configs.put("db.port",      "5432");
        configs.put("app.version",  "1.0.0");
        configs.put("max.threads",  "10");
    }

    // Public access point — ONLY WAY to get instance
    public static synchronized AppConfig getInstance() {
        if (instance == null) {
            instance = new AppConfig();  // lazy init — sirf pehli baar
        }
        return instance;
    }

    public String  get(String key)              { return configs.getOrDefault(key, "NOT_FOUND"); }
    public void    set(String key, String value) { configs.put(key, value); }
    public int     getInt(String key)            { return Integer.parseInt(get(key)); }
    public String  getEnvironment()               { return environment; }
    public void    setEnvironment(String env)     {
        if (!env.matches("development|staging|production"))
            throw new IllegalArgumentException("Valid env: development, staging, production");
        this.environment = env;
    }
    public boolean isDebugMode()                  { return debugMode; }
    public void    setDebugMode(boolean debug)    { this.debugMode = debug; }

    public static void main(String[] args) {
        // getInstance() hi ek way hai
        AppConfig config1 = AppConfig.getInstance();
        AppConfig config2 = AppConfig.getInstance();

        System.out.println("Same instance? " + (config1 == config2));  // true!

        config1.set("db.host", "prod-server.db.com");
        config1.setEnvironment("production");
        config1.setDebugMode(false);

        // config2 pe access karo — same data!
        System.out.println("DB Host:  " + config2.get("db.host"));
        System.out.println("Env:      " + config2.getEnvironment());
        System.out.println("Threads:  " + config2.getInt("max.threads"));

        try {
            config1.setEnvironment("invalid");  // invalid env
        } catch (IllegalArgumentException e) {
            System.out.println("Env Error: " + e.getMessage());
        }
    }
}
▶ Output
⚙️ AppConfig initialized (only once!) Same instance? true DB Host: prod-server.db.com Env: production Threads: 10 Env Error: Valid env: development, staging, production
9
Builder Pattern — Pizza Order Complex object (Pizza) ko step-by-step build karo. Builder pattern — method chaining se readable construction.
Builder PatternFluent API
Static inner Builder class — all fields private final — build() creates Pizza
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Pizza {
    // Immutable fields — set once via Builder
    private final String       size;
    private final String       crust;
    private final String       sauce;
    private final List<String>  toppings;
    private final boolean      extraCheese;
    private final double       price;

    // PRIVATE constructor — sirf Builder se banana possible!
    private Pizza(Builder b) {
        this.size        = b.size;
        this.crust       = b.crust;
        this.sauce       = b.sauce;
        this.toppings    = Collections.unmodifiableList(new ArrayList<>(b.toppings));
        this.extraCheese = b.extraCheese;
        this.price       = b.calculatePrice();
    }

    // ── Getters only — fully immutable ──
    public String       getSize()        { return size; }
    public String       getCrust()       { return crust; }
    public String       getSauce()       { return sauce; }
    public List<String> getToppings()    { return toppings; }
    public boolean      isExtraCheese()  { return extraCheese; }
    public double       getPrice()       { return price; }

    @Override
    public String toString() {
        return String.format(
            "🍕 %s Pizza | Crust: %s | Sauce: %s | Toppings: %s | ExtraCheese: %s | Price: Rs %.0f",
            size, crust, sauce, toppings, extraCheese, price);
    }

    // ── STATIC INNER BUILDER CLASS ──
    public static class Builder {
        // Required
        private final String size;
        // Optional with defaults
        private String       crust       = "Thin";
        private String       sauce       = "Tomato";
        private List<String> toppings    = new ArrayList<>();
        private boolean      extraCheese = false;

        public Builder(String size) {
            if (!size.matches("Small|Medium|Large"))
                throw new IllegalArgumentException("Size: Small, Medium, Large only!");
            this.size = size;
        }

        public Builder crust(String crust)    { this.crust = crust;         return this; }
        public Builder sauce(String sauce)    { this.sauce = sauce;         return this; }
        public Builder addTopping(String t)  {
            if (toppings.size() >= 5) throw new IllegalStateException("Max 5 toppings!");
            toppings.add(t); return this;
        }
        public Builder extraCheese()           { this.extraCheese = true;    return this; }

        double calculatePrice() {
            double base = size.equals("Small") ? 199 : size.equals("Medium") ? 299 : 399;
            return base + toppings.size() * 30 + (extraCheese ? 50 : 0);
        }

        public Pizza build() { return new Pizza(this); }
    }

    public static void main(String[] args) {
        // Builder pattern — readable, step by step
        Pizza p1 = new Pizza.Builder("Large")
            .crust("Thick")
            .sauce("BBQ")
            .addTopping("Mushrooms")
            .addTopping("Peppers")
            .addTopping("Onions")
            .extraCheese()
            .build();
        System.out.println(p1);

        Pizza p2 = new Pizza.Builder("Small")
            .addTopping("Paneer")
            .build();  // defaults for rest
        System.out.println(p2);
    }
}
▶ Output
🍕 Large Pizza | Crust: Thick | Sauce: BBQ | Toppings: [Mushrooms, Peppers, Onions] | ExtraCheese: true | Price: Rs 539 🍕 Small Pizza | Crust: Thin | Sauce: Tomato | Toppings: [Paneer] | ExtraCheese: false | Price: Rs 229
10
Hospital Patient Record — Complete Encapsulation Patient data — age, blood group, medical history private. Add prescription, view summary. Sensitive data strictly controlled.
HealthcareSensitive DataAdvanced
Medical records private — only doctor method can add prescriptions — summary without sensitive data
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Patient {
    private final String      patientId;
    private final String      name;
    private final int         age;
    private final String      bloodGroup;
    private       double      weight;
    private       double      height;
    private final List<String> prescriptions = new ArrayList<>();
    private final List<String> allergies     = new ArrayList<>();
    private       boolean     criticalStatus;
    private static final String[] VALID_BLOOD_GROUPS =
        {"A+","A-","B+","B-","AB+","AB-","O+","O-"};

    public Patient(String id, String name, int age, String bloodGroup, double wt, double ht) {
        if (age < 0 || age > 150) throw new IllegalArgumentException("Invalid age!");
        if (!isValidBloodGroup(bloodGroup)) throw new IllegalArgumentException("Invalid blood group!");
        if (wt <= 0 || ht <= 0)  throw new IllegalArgumentException("Weight/Height positive hona chahiye!");
        this.patientId = id; this.name = name; this.age = age;
        this.bloodGroup = bloodGroup; this.weight = wt; this.height = ht;
    }

    private boolean isValidBloodGroup(String bg) {
        for (String valid : VALID_BLOOD_GROUPS) if (valid.equals(bg)) return true;
        return false;
    }

    // Doctor privilege method — structured prescription
    public void addPrescription(String medicine, String dosage, String doctorId) {
        if (medicine == null || medicine.trim().isEmpty())
            throw new IllegalArgumentException("Medicine name required!");
        prescriptions.add(String.format("%s | %s | Dr.%s", medicine.trim(), dosage, doctorId));
        System.out.println("💊 Prescription added: " + medicine);
    }

    public void addAllergy(String allergen) {
        if (allergen == null || allergen.trim().isEmpty())
            throw new IllegalArgumentException("Allergen name required!");
        allergies.add(allergen.trim());
        System.out.println("⚠️ Allergy recorded: " + allergen);
    }

    public double getBMI() { return weight / (height * height); }
    public String getBMICategory() {
        double bmi = getBMI();
        if      (bmi < 18.5) return "Underweight";
        else if (bmi < 25.0) return "Normal";
        else if (bmi < 30.0) return "Overweight";
        else                  return "Obese";
    }

    // Getters
    public String      getPatientId()     { return patientId; }
    public String      getName()          { return name; }
    public int         getAge()           { return age; }
    public String      getBloodGroup()    { return bloodGroup; }
    public double      getWeight()        { return weight; }
    public boolean     isCritical()       { return criticalStatus; }
    public void        setCritical(boolean c) { this.criticalStatus = c; }
    // Defensive copy — caller cannot modify internal list
    public List<String> getPrescriptions() { return Collections.unmodifiableList(prescriptions); }
    public List<String> getAllergies()     { return Collections.unmodifiableList(allergies); }

    public void printSummary() {
        System.out.println("\n═══════════ PATIENT SUMMARY ═══════════");
        System.out.printf("ID: %s | Name: %s | Age: %d%n", patientId, name, age);
        System.out.printf("Blood: %s | Weight: %.1fkg | BMI: %.1f (%s)%n",
            bloodGroup, weight, getBMI(), getBMICategory());
        System.out.println("Status: " + (criticalStatus ? "🔴 CRITICAL" : "🟢 Stable"));
        System.out.println("Allergies: " + (allergies.isEmpty() ? "None" : allergies));
        System.out.println("Prescriptions:");
        for (String rx : prescriptions) System.out.println("  • " + rx);
        System.out.println("════════════════════════════════════════");
    }

    public static void main(String[] args) {
        Patient p = new Patient("PAT001", "Rahul Gupta", 35, "A+", 75.5, 1.75);
        p.addAllergy("Penicillin");
        p.addAllergy("Dust");
        p.addPrescription("Paracetamol", "500mg x 3/day", "DR123");
        p.addPrescription("Vitamin D", "1000IU x 1/day", "DR123");
        p.printSummary();

        try {
            p.getPrescriptions().add("Hacked!");  // unmodifiable!
        } catch (UnsupportedOperationException e) {
            System.out.println("Cannot modify prescriptions directly! " + e.getClass().getSimpleName());
        }
    }
}
▶ Output
⚠️ Allergy recorded: Penicillin ⚠️ Allergy recorded: Dust 💊 Prescription added: Paracetamol 💊 Prescription added: Vitamin D ═══════════ PATIENT SUMMARY ═══════════ ID: PAT001 | Name: Rahul Gupta | Age: 35 Blood: A+ | Weight: 75.5kg | BMI: 24.7 (Normal) Status: 🟢 Stable Allergies: [Penicillin, Dust] Prescriptions: • Paracetamol | 500mg x 3/day | Dr.DR123 • Vitamin D | 1000IU x 1/day | Dr.DR123 ════════════════════════════════════════ Cannot modify prescriptions directly! UnsupportedOperationException

⚠️ Common Mistakes — Inhe Hamesha Avoid Karo

Mutable Object Direct Return: public List<String> getItems() { return items; } — caller list modify kar sakta hai! Fix: return Collections.unmodifiableList(items); ya return new ArrayList<>(items);
Password Getter: Kabhi bhi getPassword() ya getPin() mat banao. Password/PIN sirf setter se set ho, verify ke liye private helper method use karo jo sirf boolean return kare.
⚠️
Trivial Getter/Setter Without Logic: Agar setter sirf this.x = x karta hai bina validation ke, aur field directly accessible hoga to encapsulation ka koi faida nahi. Validation add karo ya seriously socho ki field ko public rakhne se problem hai?
⚠️
Business Logic Bahar Rakhna: if (account.getBalance() > amount) account.setBalance(account.getBalance() - amount) — ye bahar se karna WRONG hai. account.withdraw(amount) — logic class ke andar hona chahiye!
Ek Golden Rule: Agar koi field ki value directly access karke bahar logic likhni pad rahi hai (like checking balance before withdraw), to wo logic andar move kar do. Object apni state khud manage kare — yahi encapsulation ka soul hai.
🎯
Interview Main Points: (1) Definition — private fields + public methods, (2) 4 access modifiers, (3) Getter/Setter naming convention, (4) Benefits — data hiding, validation, flexibility, (5) Real example — BankAccount ya Student class, (6) Difference from Abstraction — Abstraction = hiding complexity, Encapsulation = hiding data.