Java Programming OOP Basics
Core Concept

Java OOP Basics - Objects & Classes

Master Java Object-Oriented Programming: Learn classes, objects, constructors, methods, and the four pillars of OOP (Encapsulation, Inheritance, Polymorphism, Abstraction) with practical examples.

Objects

Real-world entities

Classes

Blueprints/templates

Encapsulation

Data hiding

Inheritance

Code reusability

1. Introduction to Object-Oriented Programming

Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around objects rather than functions and logic. Java is a pure object-oriented language that fully supports OOP principles.

Why OOP?
  • Modularity: Code organized into objects
  • Reusability: Classes can be reused
  • Maintainability: Easier to modify and maintain
  • Scalability: Easier to scale applications
  • Security: Data hiding through encapsulation
  • Flexibility: Polymorphism enables flexibility
Core OOP Concepts
  • Class: Blueprint for objects
  • Object: Instance of a class
  • Encapsulation: Bundling data with methods
  • Inheritance: Creating new classes from existing ones
  • Polymorphism: One interface, multiple implementations
  • Abstraction: Hiding implementation details

The OOP Mindset

Think in terms of real-world objects. A Car class describes what a car is (attributes: color, model) and what it can do (methods: drive(), stop()). Each actual car you see on the road is an object of that Car class.

Real-world Analogy

Class = Architectural Blueprint

  • Defines structure (rooms, doors, windows)
  • Specifies materials and dimensions
  • But it's NOT a house itself

Object = Actual House

  • Built from the blueprint
  • Has actual walls, doors, windows
  • Can be painted, furnished, occupied
  • Multiple houses can be built from same blueprint
ClassVsObject.java
// Class Definition (Blueprint)
class Car {
    // Attributes/Properties (State)
    String color;
    String model;
    int year;
    
    // Methods/Behaviors
    void startEngine() {
        System.out.println("Engine started!");
    }
    
    void drive() {
        System.out.println("Car is driving...");
    }
    
    void displayInfo() {
        System.out.println("Model: " + model + ", Color: " + color + ", Year: " + year);
    }
}

public class ClassVsObject {
    public static void main(String[] args) {
        // Creating Objects (Instances) from Class
        Car car1 = new Car();  // Object 1
        car1.color = "Red";
        car1.model = "Toyota Camry";
        car1.year = 2023;
        
        Car car2 = new Car();  // Object 2
        car2.color = "Blue";
        car2.model = "Honda Civic";
        car2.year = 2022;
        
        Car car3 = new Car();  // Object 3
        car3.color = "Black";
        car3.model = "Tesla Model S";
        car3.year = 2024;
        
        // Using objects
        System.out.println("=== Car Objects ===");
        car1.displayInfo();
        car1.startEngine();
        
        System.out.println();
        car2.displayInfo();
        car2.drive();
        
        System.out.println();
        car3.displayInfo();
        car3.startEngine();
        car3.drive();
        
        // Each object is independent
        System.out.println("\n=== Object Independence ===");
        System.out.println("car1 == car2: " + (car1 == car2));  // false
        System.out.println("car1 model: " + car1.model);
        System.out.println("car2 model: " + car2.model);
    }
}

2. Classes in Java - Complete Guide

A class is a template or blueprint for creating objects. It defines the attributes (data) and methods (functions) that the objects created from the class will have.

ClassStructure.java
// Complete Class Structure Example
public class Student {
    // ========== 1. CLASS VARIABLES (Static Fields) ==========
    static String schoolName = "Java University";
    static int totalStudents = 0;
    
    // ========== 2. INSTANCE VARIABLES (Fields/Attributes) ==========
    private String name;        // Private for encapsulation
    private int age;
    private String studentId;
    private double gpa;
    
    // ========== 3. CONSTRUCTORS ==========
    // Default Constructor
    public Student() {
        this.name = "Unknown";
        this.age = 18;
        this.studentId = generateStudentId();
        this.gpa = 0.0;
        totalStudents++;  // Update class variable
    }
    
    // Parameterized Constructor
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.studentId = generateStudentId();
        this.gpa = 0.0;
        totalStudents++;
    }
    
    // Full Parameterized Constructor
    public Student(String name, int age, double gpa) {
        this.name = name;
        this.age = age;
        this.studentId = generateStudentId();
        this.gpa = gpa;
        totalStudents++;
    }
    
    // ========== 4. METHODS ==========
    // Getter Methods (Accessors)
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    public String getStudentId() {
        return studentId;
    }
    
    public double getGpa() {
        return gpa;
    }
    
    // Setter Methods (Mutators)
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        }
    }
    
    public void setAge(int age) {
        if (age >= 0 && age <= 120) {
            this.age = age;
        }
    }
    
    public void setGpa(double gpa) {
        if (gpa >= 0.0 && gpa <= 4.0) {
            this.gpa = gpa;
        }
    }
    
    // Business Logic Methods
    public void study(String subject) {
        System.out.println(name + " is studying " + subject);
        gpa = Math.min(4.0, gpa + 0.1);  // GPA improves with studying
    }
    
    public void takeExam(String examName) {
        System.out.println(name + " is taking " + examName + " exam");
    }
    
    public String getGradeLevel() {
        if (gpa >= 3.5) return "Excellent";
        else if (gpa >= 3.0) return "Good";
        else if (gpa >= 2.0) return "Average";
        else return "Needs Improvement";
    }
    
    // Static Method (Class Method)
    public static void displaySchoolInfo() {
        System.out.println("School: " + schoolName);
        System.out.println("Total Students: " + totalStudents);
    }
    
    // Private Helper Method
    private String generateStudentId() {
        return "STU" + (1000 + totalStudents);
    }
    
    // toString() method for string representation
    @Override
    public String toString() {
        return "Student [Name: " + name + ", Age: " + age + 
               ", ID: " + studentId + ", GPA: " + gpa + "]";
    }
    
    // ========== 5. MAIN METHOD (for testing) ==========
    public static void main(String[] args) {
        System.out.println("=== Testing Student Class ===");
        
        // Create students using different constructors
        Student student1 = new Student();
        Student student2 = new Student("Alice Johnson", 20);
        Student student3 = new Student("Bob Smith", 22, 3.8);
        
        // Display student info
        System.out.println("Student 1: " + student1);
        System.out.println("Student 2: " + student2);
        System.out.println("Student 3: " + student3);
        
        // Use methods
        student1.setName("Charlie Brown");
        student1.setAge(21);
        student1.study("Java Programming");
        
        System.out.println("\nAfter studying:");
        System.out.println("Student 1 GPA: " + student1.getGpa());
        System.out.println("Student 1 Grade Level: " + student1.getGradeLevel());
        
        // Static method call
        System.out.println("\nSchool Information:");
        Student.displaySchoolInfo();
        
        // Demonstrate encapsulation
        System.out.println("\n=== Encapsulation Demonstration ===");
        // This would cause error because 'name' is private:
        // student1.name = "Direct Access";  // COMPILE ERROR
        
        // Correct way using setter
        student1.setName("Updated Name");
        System.out.println("Updated name via setter: " + student1.getName());
    }
}

Class Components Explained

Component Purpose Example Access Level Best Practices
Class Variables (static) Shared by all objects of the class static int count; Usually private Use for class-wide data
Instance Variables Unique to each object private String name; Should be private Use getters/setters for access
Constructors Initialize new objects public Student() { } Usually public Overload for flexibility
Getter Methods Read private data public String getName() Public Return copy of mutable objects
Setter Methods Modify private data public void setName() Public Validate input data
Business Methods Implement object behavior public void study() Public/Private Single responsibility principle
Static Methods Class-level operations static void displayInfo() Public/Private Don't use instance variables
Private Methods Internal helper methods private String generateId() Private Implementation details only
toString() String representation public String toString() Public Always override
UML Class Diagram: Student Class
Student
- name: String
- age: int
- studentId: String
- gpa: double
+ schoolName: String
+ totalStudents: int
+ Student()
+ Student(name: String, age: int)
+ getName(): String
+ setName(name: String): void
+ study(subject: String): void
+ getGradeLevel(): String
+ displaySchoolInfo(): void
+ toString(): String

3. Objects in Java - Creation and Usage

An object is an instance of a class. It represents a real-world entity with state (attributes) and behavior (methods).

ObjectCreation.java
// Book class for object demonstration
class Book {
    private String title;
    private String author;
    private double price;
    private int pages;
    
    // Constructors
    public Book() {
        this.title = "Unknown";
        this.author = "Unknown";
        this.price = 0.0;
        this.pages = 0;
    }
    
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
        this.price = 0.0;
        this.pages = 0;
    }
    
    public Book(String title, String author, double price, int pages) {
        this.title = title;
        this.author = author;
        this.price = price;
        this.pages = pages;
    }
    
    // Getters and Setters
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    
    public double getPrice() { return price; }
    public void setPrice(double price) { 
        if (price >= 0) this.price = price; 
    }
    
    public int getPages() { return pages; }
    public void setPages(int pages) { 
        if (pages >= 0) this.pages = pages; 
    }
    
    // Business methods
    public void applyDiscount(double percentage) {
        if (percentage > 0 && percentage <= 100) {
            price = price * (100 - percentage) / 100;
        }
    }
    
    public double getPricePerPage() {
        if (pages > 0) {
            return price / pages;
        }
        return 0;
    }
    
    @Override
    public String toString() {
        return String.format("Book: %s by %s ($%.2f, %d pages)", 
                           title, author, price, pages);
    }
}

public class ObjectCreation {
    public static void main(String[] args) {
        System.out.println("=== Different Ways to Create Objects ===\n");
        
        // Method 1: Using 'new' keyword (most common)
        Book book1 = new Book();
        book1.setTitle("Java Programming");
        book1.setAuthor("John Doe");
        book1.setPrice(49.99);
        book1.setPages(500);
        
        // Method 2: Using parameterized constructor
        Book book2 = new Book("Python Basics", "Jane Smith", 39.99, 400);
        
        // Method 3: Using constructor with partial parameters
        Book book3 = new Book("C++ Essentials", "Mike Johnson");
        book3.setPrice(44.99);
        book3.setPages(350);
        
        // Method 4: Array of objects
        Book[] library = new Book[3];
        library[0] = book1;
        library[1] = book2;
        library[2] = book3;
        
        // Method 5: Anonymous object (one-time use)
        System.out.println("Anonymous object price: $" + 
                          new Book("Quick Reference", "Author", 9.99, 100).getPrice());
        
        // Display all books
        System.out.println("\n=== Library Collection ===");
        for (Book book : library) {
            System.out.println(book);
        }
        
        // Demonstrate object behavior
        System.out.println("\n=== Object Behavior Demonstration ===");
        book1.applyDiscount(20);
        System.out.println("After 20% discount: " + book1);
        System.out.printf("Price per page: $%.4f\n", book1.getPricePerPage());
        
        // Object comparison
        System.out.println("\n=== Object Comparison ===");
        Book book4 = new Book("Java Programming", "John Doe", 49.99, 500);
        Book book5 = book1;
        
        System.out.println("book1 == book4: " + (book1 == book4));  // false (different objects)
        System.out.println("book1 == book5: " + (book1 == book5));  // true (same object)
        System.out.println("book1.equals(book4): " + book1.equals(book4));  // false (default equals())
        
        // Object null reference
        System.out.println("\n=== Null Reference Demonstration ===");
        Book nullBook = null;
        System.out.println("nullBook is: " + nullBook);
        
        // This would cause NullPointerException:
        // nullBook.getTitle();  // UNCOMMENT TO SEE ERROR
        
        // Safe way to handle null
        if (nullBook != null) {
            System.out.println("Title: " + nullBook.getTitle());
        } else {
            System.out.println("Book is null!");
        }
        
        // Garbage collection hint
        System.out.println("\n=== Memory Management ===");
        Book tempBook = new Book("Temp Book", "Temp Author", 1.99, 50);
        tempBook = null;  // Now eligible for garbage collection
        System.gc();  // Hint to JVM (not guaranteed to run immediately)
    }
}
ObjectLifecycle.java
// Bank Account example for object lifecycle
class BankAccount {
    private static int accountCounter = 1000;
    
    private final int accountNumber;  // final - can't be changed after initialization
    private String accountHolder;
    private double balance;
    private boolean isActive;
    
    // Static initialization block
    static {
        System.out.println("BankAccount class loaded. Initializing...");
    }
    
    // Instance initialization block (runs before each constructor)
    {
        System.out.println("Creating a new BankAccount object...");
        this.isActive = true;
    }
    
    // Constructors
    public BankAccount(String accountHolder) {
        this.accountNumber = ++accountCounter;  // Auto-generated
        this.accountHolder = accountHolder;
        this.balance = 0.0;
    }
    
    public BankAccount(String accountHolder, double initialBalance) {
        this.accountNumber = ++accountCounter;
        this.accountHolder = accountHolder;
        this.balance = initialBalance;
    }
    
    // Methods
    public void deposit(double amount) {
        if (amount > 0 && isActive) {
            balance += amount;
            System.out.printf("Deposited $%.2f. New balance: $%.2f\n", amount, balance);
        }
    }
    
    public void withdraw(double amount) {
        if (amount > 0 && isActive && amount <= balance) {
            balance -= amount;
            System.out.printf("Withdrew $%.2f. New balance: $%.2f\n", amount, balance);
        }
    }
    
    public void transfer(BankAccount recipient, double amount) {
        if (amount > 0 && this.isActive && recipient.isActive && amount <= balance) {
            this.balance -= amount;
            recipient.balance += amount;
            System.out.printf("Transferred $%.2f to %s\n", amount, recipient.accountHolder);
        }
    }
    
    public void deactivate() {
        this.isActive = false;
        System.out.println("Account deactivated.");
    }
    
    public void activate() {
        this.isActive = true;
        System.out.println("Account activated.");
    }
    
    // Getters
    public int getAccountNumber() { return accountNumber; }
    public String getAccountHolder() { return accountHolder; }
    public double getBalance() { return balance; }
    public boolean isActive() { return isActive; }
    
    @Override
    public String toString() {
        return String.format("Account #%d: %s - $%.2f [%s]", 
                           accountNumber, accountHolder, balance, 
                           isActive ? "Active" : "Inactive");
    }
    
    // Finalizer (deprecated in Java 9+, shown for demonstration)
    @Override
    protected void finalize() throws Throwable {
        try {
            System.out.println("Finalizing account #" + accountNumber);
        } finally {
            super.finalize();
        }
    }
}

public class ObjectLifecycle {
    public static void main(String[] args) {
        System.out.println("=== Object Lifecycle Demonstration ===\n");
        
        // 1. Object Creation
        System.out.println("1. Creating BankAccount objects:");
        BankAccount account1 = new BankAccount("Alice");
        BankAccount account2 = new BankAccount("Bob", 1000.0);
        
        System.out.println("\n2. Initial state:");
        System.out.println(account1);
        System.out.println(account2);
        
        // 3. Object Usage
        System.out.println("\n3. Using objects:");
        account1.deposit(500.0);
        account1.withdraw(200.0);
        account1.transfer(account2, 100.0);
        
        System.out.println("\n4. After transactions:");
        System.out.println(account1);
        System.out.println(account2);
        
        // 4. Object State Changes
        System.out.println("\n5. Changing object state:");
        account1.deactivate();
        account1.deposit(100.0);  // Should fail - account inactive
        
        account1.activate();
        account1.deposit(100.0);  // Should work now
        
        // 5. Object References
        System.out.println("\n6. Object references:");
        BankAccount account3 = account1;  // Both reference same object
        System.out.println("account1: " + account1.getAccountNumber());
        System.out.println("account3: " + account3.getAccountNumber());
        System.out.println("Same object? " + (account1 == account3));
        
        // 6. Making object eligible for garbage collection
        System.out.println("\n7. Making objects eligible for GC:");
        account3 = null;
        
        // Suggest garbage collection (for demonstration only)
        System.gc();
        
        // Wait a bit to see finalizer output
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("\n=== Object Lifecycle Summary ===");
        System.out.println("1. Creation: new keyword");
        System.out.println("2. Initialization: constructors, initialization blocks");
        System.out.println("3. Usage: method calls, state changes");
        System.out.println("4. Inaccessibility: null reference, out of scope");
        System.out.println("5. Garbage Collection: automatic memory reclamation");
    }
}

Object Concepts and Characteristics

Concept Description Example Key Points
Instantiation Creating an object from a class Book b = new Book(); 'new' allocates memory and calls constructor
Reference Variable Variable that holds object reference Book myBook; Stores memory address, not the object itself
Object State Current values of object attributes b.title = "Java" Changed by methods; defines current condition
Object Behavior Methods that define what object can do b.read() Implemented as methods in class
Object Identity Unique identifier for each object b.hashCode() Memory address; unique for each object
Garbage Collection Automatic memory reclamation b = null; JVM automatically frees unused objects
Object Lifetime From creation to garbage collection Create → Use → Destroy Managed by JVM; developer creates, JVM destroys

4. Constructors - Object Initialization

Constructors are special methods used to initialize newly created objects. They have the same name as the class and no return type.

ConstructorTypes.java
// Employee class demonstrating different constructor types
class Employee {
    private int id;
    private String name;
    private String department;
    private double salary;
    private String email;
    private String phone;
    
    // ========== CONSTRUCTOR TYPES ==========
    
    // 1. Default (No-arg) Constructor
    public Employee() {
        this.id = 0;
        this.name = "Unknown";
        this.department = "Unassigned";
        this.salary = 0.0;
        this.email = "no-email@company.com";
        this.phone = "000-000-0000";
        System.out.println("Default constructor called");
    }
    
    // 2. Parameterized Constructor
    public Employee(int id, String name, String department, double salary) {
        this.id = id;
        this.name = name;
        this.department = department;
        this.salary = salary;
        this.email = name.toLowerCase().replace(" ", ".") + "@company.com";
        this.phone = "000-000-0000";
        System.out.println("Parameterized constructor called");
    }
    
    // 3. Constructor Overloading (different parameter list)
    public Employee(String name, String department) {
        this(0, name, department, 0.0);  // Calling another constructor
        System.out.println("Two-parameter constructor called");
    }
    
    // 4. Copy Constructor (creates copy of another object)
    public Employee(Employee other) {
        this.id = other.id;
        this.name = other.name;
        this.department = other.department;
        this.salary = other.salary;
        this.email = other.email;
        this.phone = other.phone;
        System.out.println("Copy constructor called");
    }
    
    // 5. Constructor with Object Parameter
    public Employee(int id, String name, Department dept) {
        this.id = id;
        this.name = name;
        this.department = dept.getName();
        this.salary = dept.getBaseSalary();
        this.email = generateEmail(name);
        this.phone = "000-000-0000";
    }
    
    // ========== HELPER METHODS ==========
    private String generateEmail(String name) {
        return name.toLowerCase().replace(" ", ".") + "@company.com";
    }
    
    // ========== GETTERS AND SETTERS ==========
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { 
        this.name = name;
        this.email = generateEmail(name);
    }
    
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
    
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }
    
    public String getEmail() { return email; }
    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
    
    // ========== BUSINESS METHODS ==========
    public void giveRaise(double percentage) {
        if (percentage > 0) {
            salary += salary * percentage / 100;
            System.out.printf("%s got %.1f%% raise. New salary: $%.2f\n", 
                            name, percentage, salary);
        }
    }
    
    public void transferDepartment(String newDept) {
        System.out.printf("%s transferred from %s to %s\n", 
                         name, department, newDept);
        department = newDept;
    }
    
    @Override
    public String toString() {
        return String.format("Employee #%d: %s (%s) - $%.2f", 
                           id, name, department, salary);
    }
}

// Supporting Department class
class Department {
    private String name;
    private double baseSalary;
    
    public Department(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }
    
    public String getName() { return name; }
    public double getBaseSalary() { return baseSalary; }
}

public class ConstructorTypes {
    public static void main(String[] args) {
        System.out.println("=== Constructor Types Demonstration ===\n");
        
        // 1. Using default constructor
        System.out.println("1. Default Constructor:");
        Employee emp1 = new Employee();
        System.out.println(emp1);
        
        // 2. Using parameterized constructor
        System.out.println("\n2. Parameterized Constructor:");
        Employee emp2 = new Employee(101, "John Doe", "Engineering", 75000.0);
        System.out.println(emp2);
        System.out.println("Email: " + emp2.getEmail());
        
        // 3. Using overloaded constructor
        System.out.println("\n3. Overloaded Constructor:");
        Employee emp3 = new Employee("Jane Smith", "Marketing");
        System.out.println(emp3);
        
        // 4. Using copy constructor
        System.out.println("\n4. Copy Constructor:");
        Employee emp4 = new Employee(emp2);  // Copy of emp2
        emp4.setId(102);  // Change ID to make it different
        emp4.setName("John Doe Clone");
        System.out.println("Original: " + emp2);
        System.out.println("Copy: " + emp4);
        
        // 5. Constructor with object parameter
        System.out.println("\n5. Constructor with Object Parameter:");
        Department itDept = new Department("IT", 80000.0);
        Employee emp5 = new Employee(103, "Bob Johnson", itDept);
        System.out.println(emp5);
        
        // 6. Constructor chaining demonstration
        System.out.println("\n6. Constructor Chaining:");
        System.out.println("Note how constructors call each other using this()");
        
        // 7. Object initialization sequence
        System.out.println("\n7. Initialization Sequence:");
        System.out.println("1. Default values assigned");
        System.out.println("2. Instance initialization blocks executed");
        System.out.println("3. Constructor executed");
        
        // 8. Common constructor mistakes
        System.out.println("\n8. Common Constructor Mistakes to Avoid:");
        System.out.println("- Forgetting to initialize all fields");
        System.out.println("- Not validating input parameters");
        System.out.println("- Not calling super() in inheritance hierarchy");
        System.out.println("- Making constructors too complex");
        
        // 9. Best practices
        System.out.println("\n9. Constructor Best Practices:");
        System.out.println("- Keep constructors simple");
        System.out.println("- Overload constructors for flexibility");
        System.out.println("- Use copy constructors for immutable objects");
        System.out.println("- Consider using factory methods for complex creation");
        System.out.println("- Always validate parameters in public constructors");
    }
}
Constructor Rules & Features
  • Name: Same as class name
  • Return Type: No return type (not even void)
  • Automatic: Java provides default constructor if none defined
  • Overloading: Multiple constructors with different parameters
  • Chaining: One constructor can call another using this()
  • Inheritance: Subclass constructors must call superclass constructor
  • Access Modifiers: Can be public, protected, private, or default
Constructor Common Issues
  • Defining return type makes it a method, not constructor
  • Forgetting to initialize all instance variables
  • Not calling super() in subclass constructors
  • Complex logic in constructors (should be simple)
  • Circular constructor calls (infinite recursion)
  • Throwing exceptions without proper handling
ConstructorChaining.java
// Demonstrating constructor chaining
class Product {
    private String name;
    private double price;
    private int quantity;
    private String category;
    private String sku;
    
    // Instance initialization block
    {
        System.out.println("Instance initialization block executed");
        category = "Uncategorized";
    }
    
    // Constructor 1: Most detailed
    public Product(String name, double price, int quantity, String category, String sku) {
        System.out.println("5-parameter constructor called");
        this.name = name;
        this.price = price;
        this.quantity = quantity;
        this.category = category;
        this.sku = sku;
    }
    
    // Constructor 2: Chains to constructor 1
    public Product(String name, double price, int quantity) {
        this(name, price, quantity, "General", "SKU-" + System.currentTimeMillis());
        System.out.println("3-parameter constructor called");
    }
    
    // Constructor 3: Chains to constructor 2
    public Product(String name, double price) {
        this(name, price, 1);  // Default quantity = 1
        System.out.println("2-parameter constructor called");
    }
    
    // Constructor 4: Default constructor chains to constructor 3
    public Product() {
        this("Unknown Product", 0.0);
        System.out.println("Default constructor called");
    }
    
    // Copy constructor
    public Product(Product other) {
        this(other.name, other.price, other.quantity, other.category, other.sku);
        System.out.println("Copy constructor called");
    }
    
    @Override
    public String toString() {
        return String.format("%s - $%.2f (Qty: %d, Cat: %s)", name, price, quantity, category);
    }
}

public class ConstructorChaining {
    public static void main(String[] args) {
        System.out.println("=== Constructor Chaining Demonstration ===\n");
        
        System.out.println("Creating Product 1 (Default constructor):");
        Product p1 = new Product();
        System.out.println(p1);
        
        System.out.println("\nCreating Product 2 (2-parameter constructor):");
        Product p2 = new Product("Laptop", 999.99);
        System.out.println(p2);
        
        System.out.println("\nCreating Product 3 (3-parameter constructor):");
        Product p3 = new Product("Mouse", 25.99, 10);
        System.out.println(p3);
        
        System.out.println("\nCreating Product 4 (5-parameter constructor):");
        Product p4 = new Product("Keyboard", 79.99, 5, "Electronics", "KB-12345");
        System.out.println(p4);
        
        System.out.println("\nCreating Product 5 (Copy constructor):");
        Product p5 = new Product(p4);
        System.out.println(p5);
        
        System.out.println("\n=== Constructor Chaining Flow ===");
        System.out.println("When calling new Product(\"Test\", 10.0):");
        System.out.println("1. Default values assigned");
        System.out.println("2. Instance init block executed");
        System.out.println("3. 2-param constructor starts");
        System.out.println("4. Calls 3-param constructor via this()");
        System.out.println("5. 3-param constructor calls 5-param constructor");
        System.out.println("6. 5-param constructor executes body");
        System.out.println("7. Returns to 3-param constructor body");
        System.out.println("8. Returns to 2-param constructor body");
        System.out.println("9. Object created and returned");
    }
}

5. Methods - Object Behavior

Methods define the behavior of objects. They are functions that belong to a class and operate on object data.

MethodTypes.java
// Calculator class demonstrating different method types
class Calculator {
    // ========== INSTANCE VARIABLES ==========
    private double lastResult;
    private int operationCount;
    private String model;
    
    // ========== CONSTRUCTOR ==========
    public Calculator(String model) {
        this.model = model;
        this.lastResult = 0.0;
        this.operationCount = 0;
    }
    
    // ========== INSTANCE METHODS ==========
    // 1. Accessor Methods (Getters)
    public double getLastResult() {
        return lastResult;
    }
    
    public int getOperationCount() {
        return operationCount;
    }
    
    public String getModel() {
        return model;
    }
    
    // 2. Mutator Methods (Setters)
    public void reset() {
        lastResult = 0.0;
        operationCount = 0;
        System.out.println("Calculator reset");
    }
    
    // 3. Business Logic Methods
    public double add(double a, double b) {
        operationCount++;
        lastResult = a + b;
        return lastResult;
    }
    
    public double subtract(double a, double b) {
        operationCount++;
        lastResult = a - b;
        return lastResult;
    }
    
    public double multiply(double a, double b) {
        operationCount++;
        lastResult = a * b;
        return lastResult;
    }
    
    public double divide(double a, double b) {
        if (b != 0) {
            operationCount++;
            lastResult = a / b;
            return lastResult;
        } else {
            System.out.println("Error: Division by zero!");
            return Double.NaN;
        }
    }
    
    // Method overloading (same name, different parameters)
    public double add(double a, double b, double c) {
        return add(add(a, b), c);  // Reuse existing method
    }
    
    // Method with variable arguments (varargs)
    public double sum(double... numbers) {
        double total = 0.0;
        for (double num : numbers) {
            total += num;
        }
        operationCount++;
        lastResult = total;
        return total;
    }
    
    // Method returning complex object
    public CalculationResult calculateWithDetails(double a, double b, String operation) {
        double result = 0.0;
        switch (operation.toLowerCase()) {
            case "add": result = add(a, b); break;
            case "subtract": result = subtract(a, b); break;
            case "multiply": result = multiply(a, b); break;
            case "divide": result = divide(a, b); break;
            default: result = Double.NaN;
        }
        return new CalculationResult(a, b, operation, result);
    }
    
    // Recursive method (calls itself)
    public double power(double base, int exponent) {
        if (exponent == 0) return 1.0;
        if (exponent < 0) return 1.0 / power(base, -exponent);
        operationCount++;
        lastResult = base * power(base, exponent - 1);
        return lastResult;
    }
    
    // Method with object parameter
    public void updateModel(CalculatorConfig config) {
        if (config != null) {
            this.model = config.getModelName();
            System.out.println("Model updated to: " + model);
        }
    }
    
    // Method that throws exception
    public double safeDivide(double a, double b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("Division by zero is not allowed");
        }
        return divide(a, b);
    }
    
    // ========== STATIC METHODS ==========
    public static String getCalculatorInfo() {
        return "Basic Calculator with arithmetic operations";
    }
    
    public static double convertToDecimal(String binary) {
        return Integer.parseInt(binary, 2);
    }
    
    // Factory method (static method that creates objects)
    public static Calculator createScientificCalculator() {
        Calculator calc = new Calculator("Scientific-X100");
        return calc;
    }
    
    // ========== PRIVATE HELPER METHODS ==========
    private void incrementOperationCount() {
        operationCount++;
    }
    
    private boolean isValidNumber(double num) {
        return !Double.isNaN(num) && !Double.isInfinite(num);
    }
    
    // ========== FINAL METHOD (cannot be overridden) ==========
    public final String getManufacturer() {
        return "JavaCalc Inc.";
    }
    
    // ========== toString METHOD ==========
    @Override
    public String toString() {
        return String.format("Calculator [Model: %s, Last Result: %.2f, Operations: %d]", 
                           model, lastResult, operationCount);
    }
}

// Supporting classes
class CalculationResult {
    private double operand1;
    private double operand2;
    private String operation;
    private double result;
    
    public CalculationResult(double op1, double op2, String op, double res) {
        this.operand1 = op1;
        this.operand2 = op2;
        this.operation = op;
        this.result = res;
    }
    
    @Override
    public String toString() {
        return String.format("%.2f %s %.2f = %.2f", operand1, operation, operand2, result);
    }
}

class CalculatorConfig {
    private String modelName;
    private boolean scientific;
    
    public CalculatorConfig(String modelName, boolean scientific) {
        this.modelName = modelName;
        this.scientific = scientific;
    }
    
    public String getModelName() { return modelName; }
    public boolean isScientific() { return scientific; }
}

public class MethodTypes {
    public static void main(String[] args) {
        System.out.println("=== Method Types Demonstration ===\n");
        
        // Create calculator
        Calculator calc = new Calculator("Basic-2000");
        System.out.println("Created: " + calc);
        
        // Instance method calls
        System.out.println("\n=== Instance Methods ===");
        System.out.println("5 + 3 = " + calc.add(5, 3));
        System.out.println("10 - 4 = " + calc.subtract(10, 4));
        System.out.println("6 * 7 = " + calc.multiply(6, 7));
        System.out.println("15 / 3 = " + calc.divide(15, 3));
        
        // Method overloading
        System.out.println("\n=== Method Overloading ===");
        System.out.println("Sum of 1, 2, 3 = " + calc.add(1, 2, 3));
        
        // Varargs method
        System.out.println("\n=== Variable Arguments ===");
        System.out.println("Sum of 1, 2, 3, 4, 5 = " + calc.sum(1, 2, 3, 4, 5));
        
        // Method returning object
        System.out.println("\n=== Method Returning Object ===");
        CalculationResult result = calc.calculateWithDetails(10, 2, "divide");
        System.out.println("Calculation Result: " + result);
        
        // Recursive method
        System.out.println("\n=== Recursive Method ===");
        System.out.println("2^5 = " + calc.power(2, 5));
        
        // Static method call
        System.out.println("\n=== Static Methods ===");
        System.out.println("Calculator Info: " + Calculator.getCalculatorInfo());
        System.out.println("Binary 1010 to decimal: " + Calculator.convertToDecimal("1010"));
        
        // Factory method
        System.out.println("\n=== Factory Method ===");
        Calculator sciCalc = Calculator.createScientificCalculator();
        System.out.println("Created: " + sciCalc);
        
        // Method with exception
        System.out.println("\n=== Method with Exception ===");
        try {
            System.out.println("10 / 0 = " + calc.safeDivide(10, 0));
        } catch (ArithmeticException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
        
        // Final method
        System.out.println("\n=== Final Method ===");
        System.out.println("Manufacturer: " + calc.getManufacturer());
        
        // Method summary
        System.out.println("\n=== Method Summary ===");
        System.out.println("Total operations performed: " + calc.getOperationCount());
        System.out.println("Last result: " + calc.getLastResult());
        
        // Reset and final state
        calc.reset();
        System.out.println("\nAfter reset: " + calc);
    }
}

Method Types and Characteristics

Method Type Purpose Example Key Features
Instance Methods Operate on object instance public void drive() Access instance variables, use 'this' keyword
Static Methods Class-level operations static int getCount() Cannot access instance variables, called via class name
Getter Methods Return private field values public String getName() Follow naming convention: getFieldName()
Setter Methods Modify private field values public void setName() Validate input, follow naming convention
Factory Methods Create and return objects static Car createSportsCar() Often static, provide controlled object creation
Overloaded Methods Same name, different parameters add(int a, int b)
add(double a, double b)
Compile-time polymorphism, different signatures
Recursive Methods Call themselves factorial(int n) Need base case, can cause stack overflow
Final Methods Cannot be overridden public final void process() Used for security, performance, or design
Abstract Methods No implementation, must be overridden abstract void draw() Only in abstract classes/interfaces

6. The Four Pillars of OOP

1. Encapsulation

Bundling data with methods that operate on that data.

  • Hide internal state
  • Control access via getters/setters
  • Maintain data integrity
  • Reduce complexity
EncapsulationDemo.java
// Bank Account with proper encapsulation
class BankAccount {
    // Private fields (encapsulated)
    private String accountNumber;
    private String accountHolder;
    private double balance;
    private String password;
    
    // Constructor
    public BankAccount(String accountHolder, String password) {
        this.accountNumber = "ACC" + System.currentTimeMillis();
        this.accountHolder = accountHolder;
        this.balance = 0.0;
        setPassword(password);  // Use setter for validation
    }
    
    // Public getters (controlled access)
    public String getAccountNumber() { return accountNumber; }
    public String getAccountHolder() { return accountHolder; }
    public double getBalance() { return balance; }
    
    // No direct getter for password (security)
    
    // Public setters with validation
    public void setAccountHolder(String accountHolder) {
        if (accountHolder != null && !accountHolder.trim().isEmpty()) {
            this.accountHolder = accountHolder;
        }
    }
    
    private void setPassword(String password) {
        if (password != null && password.length() >= 8) {
            this.password = password;
        } else {
            throw new IllegalArgumentException("Password must be at least 8 characters");
        }
    }
    
    // Public methods for operations (business logic)
    public boolean deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            return true;
        }
        return false;
    }
    
    public boolean withdraw(double amount, String inputPassword) {
        if (authenticate(inputPassword) && amount > 0 && amount <= balance) {
            balance -= amount;
            return true;
        }
        return false;
    }
    
    public boolean changePassword(String oldPassword, String newPassword) {
        if (authenticate(oldPassword)) {
            setPassword(newPassword);
            return true;
        }
        return false;
    }
    
    // Private helper method
    private boolean authenticate(String inputPassword) {
        return this.password.equals(inputPassword);
    }
    
    @Override
    public String toString() {
        return String.format("Account: %s, Holder: %s, Balance: $%.2f", 
                           accountNumber, accountHolder, balance);
    }
}

public class EncapsulationDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("John Doe", "secure123");
        
        // Can't access private fields directly
        // account.balance = 1000;  // COMPILE ERROR
        // System.out.println(account.password);  // COMPILE ERROR
        
        // Must use public methods
        account.deposit(1000.0);
        System.out.println("Balance: $" + account.getBalance());
        
        boolean success = account.withdraw(200.0, "secure123");
        System.out.println("Withdrawal successful: " + success);
        System.out.println("New balance: $" + account.getBalance());
        
        // Attempt invalid withdrawal
        success = account.withdraw(2000.0, "secure123");
        System.out.println("Large withdrawal: " + success);
        
        // Attempt with wrong password
        success = account.withdraw(100.0, "wrongpass");
        System.out.println("Wrong password withdrawal: " + success);
        
        System.out.println("\n" + account);
    }
}
2. Inheritance

Creating new classes from existing classes.

  • Code reuse
  • IS-A relationship
  • Method overriding
  • Polymorphism foundation
InheritanceDemo.java
// Base class
class Vehicle {
    private String make;
    private String model;
    private int year;
    
    public Vehicle(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }
    
    public void start() {
        System.out.println("Vehicle starting...");
    }
    
    public void stop() {
        System.out.println("Vehicle stopping...");
    }
    
    public void displayInfo() {
        System.out.printf("%s %s (%d)\n", make, model, year);
    }
    
    // Getters
    public String getMake() { return make; }
    public String getModel() { return model; }
    public int getYear() { return year; }
}

// Derived class 1
class Car extends Vehicle {
    private int doors;
    private String fuelType;
    
    public Car(String make, String model, int year, int doors, String fuelType) {
        super(make, model, year);  // Call parent constructor
        this.doors = doors;
        this.fuelType = fuelType;
    }
    
    // Method overriding
    @Override
    public void start() {
        System.out.println("Car engine starting with key...");
    }
    
    // Additional method specific to Car
    public void honk() {
        System.out.println("Beep beep!");
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();  // Call parent method
        System.out.printf("Doors: %d, Fuel: %s\n", doors, fuelType);
    }
}

// Derived class 2
class Motorcycle extends Vehicle {
    private boolean hasSidecar;
    
    public Motorcycle(String make, String model, int year, boolean hasSidecar) {
        super(make, model, year);
        this.hasSidecar = hasSidecar;
    }
    
    @Override
    public void start() {
        System.out.println("Motorcycle starting with kick...");
    }
    
    // Additional method specific to Motorcycle
    public void wheelie() {
        System.out.println("Doing a wheelie!");
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("Has sidecar: " + hasSidecar);
    }
}

// Multi-level inheritance
class SportsCar extends Car {
    private int topSpeed;
    
    public SportsCar(String make, String model, int year, int topSpeed) {
        super(make, model, year, 2, "Premium");  // Sports cars have 2 doors
        this.topSpeed = topSpeed;
    }
    
    @Override
    public void start() {
        System.out.println("Sports car roaring to life!");
    }
    
    public void turboBoost() {
        System.out.println("Turbo boost activated!");
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("Top speed: " + topSpeed + " mph");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        System.out.println("=== Inheritance Demonstration ===\n");
        
        // Create objects of different types
        Vehicle vehicle = new Vehicle("Generic", "Model", 2020);
        Car car = new Car("Toyota", "Camry", 2023, 4, "Gasoline");
        Motorcycle bike = new Motorcycle("Harley", "Davidson", 2022, false);
        SportsCar sportsCar = new SportsCar("Ferrari", "488", 2024, 205);
        
        // Demonstrate inheritance
        System.out.println("1. Base class method:");
        vehicle.start();
        vehicle.displayInfo();
        
        System.out.println("\n2. Car (inherits from Vehicle):");
        car.start();  // Overridden method
        car.honk();   // Car-specific method
        car.displayInfo();
        
        System.out.println("\n3. Motorcycle (inherits from Vehicle):");
        bike.start();    // Overridden method
        bike.wheelie();  // Motorcycle-specific method
        bike.displayInfo();
        
        System.out.println("\n4. SportsCar (inherits from Car which inherits from Vehicle):");
        sportsCar.start();      // Overridden method
        sportsCar.honk();       // Inherited from Car
        sportsCar.turboBoost(); // SportsCar-specific method
        sportsCar.displayInfo();
        
        System.out.println("\n5. Polymorphism demonstration:");
        Vehicle[] vehicles = new Vehicle[3];
        vehicles[0] = car;
        vehicles[1] = bike;
        vehicles[2] = sportsCar;
        
        for (Vehicle v : vehicles) {
            v.start();  // Calls appropriate overridden method
        }
        
        System.out.println("\n6. instanceof operator:");
        System.out.println("car instanceof Vehicle: " + (car instanceof Vehicle));
        System.out.println("car instanceof Car: " + (car instanceof Car));
        System.out.println("car instanceof SportsCar: " + (car instanceof SportsCar));
        
        System.out.println("\n7. Type casting:");
        if (sportsCar instanceof Car) {
            Car c = (Car) sportsCar;  // Upcasting (implicit)
            c.honk();
            
            // Downcasting (explicit, check with instanceof first)
            if (c instanceof SportsCar) {
                SportsCar sc = (SportsCar) c;
                sc.turboBoost();
            }
        }
    }
}
3. Polymorphism

One interface, multiple implementations.

  • Method overriding (runtime)
  • Method overloading (compile-time)
  • Interface implementation
  • Dynamic method dispatch
PolymorphismDemo.java
// Shape hierarchy demonstrating polymorphism
abstract class Shape {
    private String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    public String getColor() {
        return color;
    }
    
    // Abstract methods (must be implemented by subclasses)
    public abstract double getArea();
    public abstract double getPerimeter();
    
    // Concrete method
    public void display() {
        System.out.println("Color: " + color);
        System.out.printf("Area: %.2f, Perimeter: %.2f\n", getArea(), getPerimeter());
    }
}

class Circle extends Shape {
    private double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
    
    // Additional method specific to Circle
    public double getDiameter() {
        return 2 * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;
    
    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * (width + height);
    }
    
    // Additional method specific to Rectangle
    public boolean isSquare() {
        return width == height;
    }
}

class Triangle extends Shape {
    private double side1, side2, side3;
    
    public Triangle(String color, double side1, double side2, double side3) {
        super(color);
        this.side1 = side1;
        this.side2 = side2;
        this.side3 = side3;
    }
    
    @Override
    public double getArea() {
        // Using Heron's formula
        double s = getPerimeter() / 2;
        return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
    }
    
    @Override
    public double getPerimeter() {
        return side1 + side2 + side3;
    }
    
    // Additional method specific to Triangle
    public String getTriangleType() {
        if (side1 == side2 && side2 == side3) return "Equilateral";
        else if (side1 == side2 || side2 == side3 || side1 == side3) return "Isosceles";
        else return "Scalene";
    }
}

// Interface for drawing
interface Drawable {
    void draw();
    void setLineWidth(int width);
}

// Multiple inheritance through interfaces
class DrawableCircle extends Circle implements Drawable {
    private int lineWidth;
    
    public DrawableCircle(String color, double radius) {
        super(color, radius);
        this.lineWidth = 1;
    }
    
    @Override
    public void draw() {
        System.out.printf("Drawing circle with radius %.2f and line width %d\n", 
                         getDiameter() / 2, lineWidth);
    }
    
    @Override
    public void setLineWidth(int width) {
        this.lineWidth = width;
    }
}

public class PolymorphismDemo {
    public static void main(String[] args) {
        System.out.println("=== Polymorphism Demonstration ===\n");
        
        // Create shapes using polymorphism
        Shape[] shapes = new Shape[4];
        shapes[0] = new Circle("Red", 5.0);
        shapes[1] = new Rectangle("Blue", 4.0, 6.0);
        shapes[2] = new Triangle("Green", 3.0, 4.0, 5.0);
        shapes[3] = new DrawableCircle("Yellow", 7.0);
        
        // Process shapes polymorphically
        System.out.println("Processing all shapes:");
        for (Shape shape : shapes) {
            System.out.println("\n--- " + shape.getClass().getSimpleName() + " ---");
            shape.display();  // Calls appropriate overridden method
            
            // Type checking and casting
            if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                System.out.println("Diameter: " + circle.getDiameter());
            }
            
            if (shape instanceof Rectangle) {
                Rectangle rect = (Rectangle) shape;
                System.out.println("Is square? " + rect.isSquare());
            }
            
            if (shape instanceof Triangle) {
                Triangle tri = (Triangle) shape;
                System.out.println("Triangle type: " + tri.getTriangleType());
            }
            
            if (shape instanceof Drawable) {
                Drawable drawable = (Drawable) shape;
                drawable.setLineWidth(2);
                drawable.draw();
            }
        }
        
        // Method overloading (compile-time polymorphism)
        System.out.println("\n=== Method Overloading ===");
        Calculator calc = new Calculator();
        System.out.println("add(5, 3): " + calc.add(5, 3));
        System.out.println("add(5.5, 3.2): " + calc.add(5.5, 3.2));
        System.out.println("add(1, 2, 3): " + calc.add(1, 2, 3));
        
        // Interface polymorphism
        System.out.println("\n=== Interface Polymorphism ===");
        Drawable drawable = new DrawableCircle("Purple", 3.0);
        drawable.setLineWidth(3);
        drawable.draw();
        
        // Using shape as Drawable
        if (shapes[3] instanceof Drawable) {
            ((Drawable) shapes[3]).draw();
        }
    }
}

// Helper Calculator class for overloading demonstration
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;
    }
}
4. Abstraction

Hiding implementation details, showing only functionality.

  • Abstract classes
  • Interfaces
  • Hide complex reality
  • Expose only essential features
AbstractionDemo.java
// Database abstraction layer
// Abstract class
abstract class Database {
    protected String connectionString;
    protected boolean isConnected;
    
    public Database(String connectionString) {
        this.connectionString = connectionString;
        this.isConnected = false;
    }
    
    // Abstract methods (implementation hidden)
    public abstract boolean connect();
    public abstract boolean disconnect();
    public abstract ResultSet executeQuery(String query);
    public abstract int executeUpdate(String query);
    
    // Concrete methods
    public boolean isConnected() {
        return isConnected;
    }
    
    public String getConnectionString() {
        return connectionString;
    }
    
    // Template method pattern
    public final void performTransaction(String... queries) {
        connect();
        try {
            for (String query : queries) {
                executeUpdate(query);
            }
            System.out.println("Transaction completed successfully");
        } finally {
            disconnect();
        }
    }
}

// Interface for logging
interface Loggable {
    void log(String message);
    void setLogLevel(String level);
}

// Concrete implementation 1
class MySQLDatabase extends Database implements Loggable {
    private String logLevel;
    
    public MySQLDatabase(String host, String database, String user, String password) {
        super(String.format("jdbc:mysql://%s/%s?user=%s&password=%s", 
                           host, database, user, password));
        this.logLevel = "INFO";
    }
    
    @Override
    public boolean connect() {
        System.out.println("Connecting to MySQL database...");
        // Simulate connection logic
        try {
            Thread.sleep(1000);  // Simulate connection delay
            isConnected = true;
            log("Connected to MySQL database");
            return true;
        } catch (InterruptedException e) {
            log("Connection failed: " + e.getMessage());
            return false;
        }
    }
    
    @Override
    public boolean disconnect() {
        System.out.println("Disconnecting from MySQL...");
        isConnected = false;
        log("Disconnected from MySQL");
        return true;
    }
    
    @Override
    public ResultSet executeQuery(String query) {
        log("Executing query: " + query);
        // Simulate query execution
        System.out.println("Executing MySQL query: " + query);
        return new ResultSet();  // Simplified
    }
    
    @Override
    public int executeUpdate(String query) {
        log("Executing update: " + query);
        // Simulate update execution
        System.out.println("Executing MySQL update: " + query);
        return 1;  // Simplified - assume 1 row affected
    }
    
    // Loggable interface implementation
    @Override
    public void log(String message) {
        System.out.println("[" + logLevel + "] MySQL: " + message);
    }
    
    @Override
    public void setLogLevel(String level) {
        this.logLevel = level;
    }
    
    // MySQL-specific method
    public void enableSSL() {
        System.out.println("SSL enabled for MySQL connection");
    }
}

// Concrete implementation 2
class PostgreSQLDatabase extends Database {
    public PostgreSQLDatabase(String host, String database, String user, String password) {
        super(String.format("jdbc:postgresql://%s/%s?user=%s&password=%s", 
                           host, database, user, password));
    }
    
    @Override
    public boolean connect() {
        System.out.println("Connecting to PostgreSQL database...");
        // PostgreSQL-specific connection logic
        isConnected = true;
        return true;
    }
    
    @Override
    public boolean disconnect() {
        System.out.println("Disconnecting from PostgreSQL...");
        isConnected = false;
        return true;
    }
    
    @Override
    public ResultSet executeQuery(String query) {
        System.out.println("Executing PostgreSQL query: " + query);
        return new ResultSet();
    }
    
    @Override
    public int executeUpdate(String query) {
        System.out.println("Executing PostgreSQL update: " + query);
        return 1;
    }
    
    // PostgreSQL-specific method
    public void enableJSONSupport() {
        System.out.println("JSON support enabled");
    }
}

// Simplified ResultSet class for demonstration
class ResultSet {
    public boolean next() { return false; }
    public String getString(int column) { return ""; }
}

public class AbstractionDemo {
    public static void main(String[] args) {
        System.out.println("=== Abstraction Demonstration ===\n");
        
        // Using abstraction - we don't care about implementation details
        Database db1 = new MySQLDatabase("localhost", "mydb", "admin", "password123");
        Database db2 = new PostgreSQLDatabase("localhost", "mydb", "admin", "password123");
        
        System.out.println("1. MySQL Database Operations:");
        System.out.println("Connection string: " + db1.getConnectionString());
        db1.connect();
        db1.executeQuery("SELECT * FROM users");
        db1.performTransaction(
            "INSERT INTO users (name, email) VALUES ('John', 'john@email.com')",
            "UPDATE users SET active = true WHERE name = 'John'"
        );
        
        System.out.println("\n2. PostgreSQL Database Operations:");
        System.out.println("Connection string: " + db2.getConnectionString());
        db2.connect();
        db2.executeQuery("SELECT * FROM products");
        
        // Accessing implementation-specific features
        System.out.println("\n3. Implementation-Specific Features:");
        if (db1 instanceof MySQLDatabase) {
            MySQLDatabase mysql = (MySQLDatabase) db1;
            mysql.setLogLevel("DEBUG");
            mysql.enableSSL();
            mysql.log("Testing debug logging");
        }
        
        if (db2 instanceof PostgreSQLDatabase) {
            PostgreSQLDatabase pg = (PostgreSQLDatabase) db2;
            pg.enableJSONSupport();
        }
        
        System.out.println("\n4. Benefits of Abstraction:");
        System.out.println("- Hide database-specific implementation details");
        System.out.println("- Provide consistent interface for all databases");
        System.out.println("- Easy to switch database implementations");
        System.out.println("- Focus on what needs to be done, not how");
        
        System.out.println("\n5. Real-world analogy:");
        System.out.println("Think of a car's steering wheel:");
        System.out.println("- User knows HOW to turn it (abstraction)");
        System.out.println("- User doesn't need to know the complex");
        System.out.println("  mechanical linkage underneath (implementation)");
    }
}

The Four Pillars of OOP - Summary

Encapsulation

Data hiding, controlled access through methods

Inheritance

Code reuse, IS-A relationship, hierarchy

Polymorphism

One interface, many forms, method overriding

Abstraction

Hide complexity, show essentials, interfaces

7. Best Practices & Common Mistakes

Common OOP Mistakes:
  1. God Objects: Classes that do too much (violates Single Responsibility)
  2. Anemic Domain Model: Classes with only getters/setters, no behavior
  3. Inheritance Misuse: Using inheritance for code reuse instead of IS-A relationship
  4. Public Fields: Exposing fields directly without encapsulation
  5. Circular Dependencies: Two classes depending on each other
  6. Over-engineering: Creating unnecessary abstraction layers
OOP Design Principles
  • SRP: Single Responsibility Principle
  • OCP: Open/Closed Principle
  • LSP: Liskov Substitution Principle
  • ISP: Interface Segregation Principle
  • DIP: Dependency Inversion Principle
  • DRY: Don't Repeat Yourself
  • KISS: Keep It Simple, Stupid
  • YAGNI: You Aren't Gonna Need It
Java OOP Best Practices
  • Use meaningful class and method names
  • Keep classes small and focused
  • Prefer composition over inheritance
  • Make fields private with getters/setters
  • Use final for immutable fields
  • Override equals(), hashCode(), and toString()
  • Use interfaces for abstraction
  • Document with Javadoc comments
OOPSolidPrinciples.java
// Demonstrating SOLID principles in Java OOP
public class OOPSolidPrinciples {
    
    // ========== SINGLE RESPONSIBILITY PRINCIPLE ==========
    // A class should have only one reason to change
    
    // BAD: One class doing multiple things
    class BadEmployee {
        private String name;
        private double salary;
        
        public void calculateSalary() { /* ... */ }
        public void saveToDatabase() { /* ... */ }  // Different responsibility!
        public void sendEmail() { /* ... */ }       // Another responsibility!
        public void generateReport() { /* ... */ }  // Yet another!
    }
    
    // GOOD: Separate classes for separate responsibilities
    class GoodEmployee {
        private String name;
        private double salary;
        // Only employee-related logic
    }
    
    class EmployeeRepository {
        public void save(GoodEmployee emp) { /* Database operations */ }
    }
    
    class EmailService {
        public void sendEmail(GoodEmployee emp, String message) { /* Email logic */ }
    }
    
    class ReportGenerator {
        public void generateReport(GoodEmployee emp) { /* Report logic */ }
    }
    
    // ========== OPEN/CLOSED PRINCIPLE ==========
    // Open for extension, closed for modification
    
    interface Discount {
        double apply(double price);
    }
    
    class RegularDiscount implements Discount {
        @Override
        public double apply(double price) {
            return price * 0.9;  // 10% discount
        }
    }
    
    class PremiumDiscount implements Discount {
        @Override
        public double apply(double price) {
            return price * 0.8;  // 20% discount
        }
    }
    
    class HolidayDiscount implements Discount {
        @Override
        public double apply(double price) {
            return price * 0.7;  // 30% discount
        }
    }
    
    // Adding new discount type doesn't require modifying existing code
    
    // ========== LISKOV SUBSTITUTION PRINCIPLE ==========
    // Subtypes must be substitutable for their base types
    
    class Bird {
        public void fly() {
            System.out.println("Flying...");
        }
    }
    
    // BAD: Penguin can't fly but inherits from Bird
    class Penguin extends Bird {
        @Override
        public void fly() {
            throw new UnsupportedOperationException("Penguins can't fly!");
        }
    }
    
    // GOOD: Separate interfaces
    interface FlyingBird {
        void fly();
    }
    
    interface SwimmingBird {
        void swim();
    }
    
    class Eagle implements FlyingBird {
        @Override
        public void fly() {
            System.out.println("Eagle flying high");
        }
    }
    
    class GoodPenguin implements SwimmingBird {
        @Override
        public void swim() {
            System.out.println("Penguin swimming");
        }
    }
    
    // ========== INTERFACE SEGREGATION PRINCIPLE ==========
    // Clients shouldn't be forced to depend on interfaces they don't use
    
    // BAD: One large interface
    interface BadWorker {
        void work();
        void eat();
        void sleep();
        void code();
        void test();
        void deploy();
    }
    
    // GOOD: Segregated interfaces
    interface Worker {
        void work();
    }
    
    interface Eater {
        void eat();
    }
    
    interface Sleeper {
        void sleep();
    }
    
    interface Coder {
        void code();
    }
    
    interface Tester {
        void test();
    }
    
    interface Deployer {
        void deploy();
    }
    
    // A class can implement only what it needs
    class Developer implements Worker, Coder, Eater, Sleeper {
        // Implement only required methods
    }
    
    // ========== DEPENDENCY INVERSION PRINCIPLE ==========
    // Depend on abstractions, not concretions
    
    // BAD: Direct dependency on concrete class
    class BadLightBulb {
        public void turnOn() { /* ... */ }
        public void turnOff() { /* ... */ }
    }
    
    class BadSwitch {
        private BadLightBulb bulb;
        
        public BadSwitch() {
            this.bulb = new BadLightBulb();  // Direct dependency
        }
        
        public void operate() {
            // Can only work with LightBulb
        }
    }
    
    // GOOD: Depend on abstraction
    interface Switchable {
        void turnOn();
        void turnOff();
    }
    
    class GoodLightBulb implements Switchable {
        @Override
        public void turnOn() { System.out.println("Light bulb on"); }
        
        @Override
        public void turnOff() { System.out.println("Light bulb off"); }
    }
    
    class Fan implements Switchable {
        @Override
        public void turnOn() { System.out.println("Fan on"); }
        
        @Override
        public void turnOff() { System.out.println("Fan off"); }
    }
    
    class GoodSwitch {
        private Switchable device;
        
        public GoodSwitch(Switchable device) {  // Dependency injection
            this.device = device;  // Can work with any Switchable
        }
        
        public void operate() {
            device.turnOn();
            // ... later
            device.turnOff();
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== SOLID Principles in Java ===");
        
        // Demonstrate Dependency Inversion
        GoodLightBulb bulb = new GoodLightBulb();
        Fan fan = new Fan();
        
        GoodSwitch lightSwitch = new GoodSwitch(bulb);
        GoodSwitch fanSwitch = new GoodSwitch(fan);
        
        System.out.println("Operating light switch:");
        lightSwitch.operate();
        
        System.out.println("\nOperating fan switch:");
        fanSwitch.operate();
        
        System.out.println("\n=== Key Takeaways ===");
        System.out.println("1. SRP: One class, one responsibility");
        System.out.println("2. OCP: Extend, don't modify");
        System.out.println("3. LSP: Substitutability is key");
        System.out.println("4. ISP: Small, focused interfaces");
        System.out.println("5. DIP: Depend on abstractions");
        
        System.out.println("\n=== When to Use What ===");
        System.out.println("Use inheritance for IS-A relationships");
        System.out.println("Use composition for HAS-A relationships");
        System.out.println("Use interfaces for CAN-DO relationships");
        System.out.println("Use abstract classes for partial implementations");
    }
}

Java OOP Mastery Checklist

Classes & Objects
  • Can create classes with proper structure
  • Understand object lifecycle
  • Know when to use static vs instance
Constructors & Methods
  • Can create multiple constructors
  • Understand method overloading/overriding
  • Know access modifiers usage
OOP Principles
  • Implement encapsulation properly
  • Use inheritance judiciously
  • Apply polymorphism effectively
  • Create appropriate abstractions

8. Practice Exercises

Exercise 1: Create a Library Management System

Design and implement a library system with the following requirements:

  1. Create a Book class with title, author, ISBN, and availability status
  2. Create a Member class with name, member ID, and list of borrowed books
  3. Create a Library class that manages books and members
  4. Implement methods to:
    • Add/remove books from library
    • Register new members
    • Borrow and return books
    • Search books by title/author
    • Display overdue books
  5. Use proper encapsulation with getters/setters
  6. Implement inheritance if appropriate (e.g., different book types)
  7. Use polymorphism for different member types (Student, Faculty)
Exercise 2: E-commerce Product System

Create an e-commerce product hierarchy:

  1. Create an abstract Product class with name, price, and description
  2. Create concrete classes: Electronics, Clothing, Book
  3. Each product type should have specific attributes:
    • Electronics: warranty period, power consumption
    • Clothing: size, material, color
    • Book: author, publisher, ISBN
  4. Create an interface Discountable with method applyDiscount()
  5. Create a ShoppingCart class that can hold different products
  6. Implement methods to:
    • Add/remove products from cart
    • Calculate total price
    • Apply discounts to eligible products
    • Display cart contents
Exercise 3: Bank Account Hierarchy

Implement a banking system with different account types:

  1. Create an abstract BankAccount class with account number, balance, and account holder
  2. Create concrete classes: SavingsAccount, CheckingAccount, LoanAccount
  3. Each account type should have:
    • SavingsAccount: interest rate, minimum balance
    • CheckingAccount: overdraft limit, transaction fee
    • LoanAccount: interest rate, loan term, collateral
  4. Create an interface InterestBearing with method calculateInterest()
  5. Create a Bank class that manages all accounts
  6. Implement methods to:
    • Open/close accounts
    • Deposit/withdraw funds
    • Transfer between accounts
    • Calculate and apply interest
    • Generate account statements