• Immutable Class in Java – Complete Guide with Examples

    An Immutable Class in Java is a class whose instances (objects) cannot be modified after creation. Once an object of an immutable class is created, its state remains constant throughout the lifetime of the object. Immutable objects are widely used in multi-threaded environments because they are inherently thread-safe and don’t require synchronization.

    ✅ Why Use Immutable Class?

    • Thread-safety: Immutable objects can be safely shared between multiple threads without additional synchronization.
    • Caching and Performance: Immutable objects can be cached and reused, reducing the need to create new objects frequently.
    • Security: Since the object’s state can’t be changed, it prevents accidental or malicious modifications.

    ✅ Key Characteristics of Immutable Class

    1. Final Class: The class should be declared final to prevent subclassing which might override methods and break immutability.
    2. Private Final Fields: All fields should be declared private and final to prevent direct access and modification.
    3. No Setter Methods: Do not provide setters. Only provide getters to expose field values.
    4. Deep Copy in Constructor: When the class has mutable fields (like arrays or collections), perform deep copying in the constructor and in the getter methods to prevent external modification.

    ✅ Example of Immutable Class

    import java.util.Date;
    
    public final class Employee {
    
        private final int id;
        private final String name;
        private final Date joiningDate;
    
        // Constructor performs deep copy of mutable objects
        public Employee(int id, String name, Date joiningDate) {
            this.id = id;
            this.name = name;
            this.joiningDate = new Date(joiningDate.getTime()); // Defensive copy
        }
    
        public int getId() {
            return id;
        }
    
        public String getName() {
            return name;
        }
    
        public Date getJoiningDate() {
            return new Date(joiningDate.getTime()); // Return copy to maintain immutability
        }
    
        @Override
        public String toString() {
            return "Employee { " +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", joiningDate=" + joiningDate +
                    " }";
        }
    }
    

    ✅ Usage Example

    import java.util.Date;
    
    public class Main {
        public static void main(String[] args) {
            Date date = new Date();
            Employee emp = new Employee(101, "John Doe", date);
    
            System.out.println(emp);
    
            // Attempt to modify original date object
            date.setTime(0);
    
            // The internal state of Employee remains unchanged
            System.out.println(emp);
    
            // Attempt to modify date retrieved via getter
            emp.getJoiningDate().setTime(0);
    
            // Employee’s joiningDate remains immutable
            System.out.println(emp);
        }
    }
    

    ✅ Output Explanation

    • Even though we modify the original Date object and the one obtained from getJoiningDate(), the internal state of the Employee object remains unchanged.
    • This demonstrates how defensive copying preserves immutability.

    ✅ Best Practices for Immutable Class

    • Use primitive types and immutable objects (like String, Integer) for fields whenever possible.
    • For fields that are mutable objects (like Date, arrays, or collections), always use deep copy in constructors and getters.
    • Mark the class as final to prevent subclassing.
    • Avoid exposing mutable fields directly.

    ✅ When Should You Use Immutable Classes?

    • When thread-safety is crucial.
    • When object state should not change after creation.
    • For value objects such as coordinates, currency, or configurations.
    • To avoid defensive copying outside your control.

    ✅ Advantages and Disadvantages

    AdvantagesDisadvantages
    Thread-safe by designHigher memory footprint if many copies needed
    Simple to reason about object statePerformance overhead due to object copying
    Can be safely shared across threadsCannot be partially updated, needs recreation

    ✅ Conclusion

    Immutable classes are a cornerstone of robust and thread-safe application design in Java. They offer simplicity, safety, and reliability in concurrent environments. However, they should be used judiciously where immutability is beneficial, and care must be taken with mutable fields.

  • Abstract Class in Java

    What is an Abstract Class in Java?

    An Abstract Class is a class in Java that cannot be instantiated directly. It is used as a base class and is meant to be extended by other classes. Abstract classes can contain abstract methods (without implementation) and concrete methods (with implementation).

    An abstract class helps in providing a common template to its child classes and enforcing some method implementation through abstraction, but still allowing common method implementations.

    Why Use Abstract Classes?

    • Abstract classes allow partial implementation of functionality that is common to multiple subclasses, reducing code duplication.
    • They also help enforce method overriding by requiring subclasses to implement abstract methods.
    • By using abstract classes, you follow key OOP principles, such as Abstraction and promoting Code Reusability.

    Abstract Class Syntax Example

    // Abstract class
    abstract class Animal {
        
        // Abstract method (must be implemented by subclass)
        abstract void sound();
        
        // Concrete method (common functionality)
        void eat() {
            System.out.println("This animal eats food");
        }
    }
    
    // Concrete subclass
    class Dog extends Animal {
        
        // Implementation of abstract method
        void sound() {
            System.out.println("Dog barks");
        }
    }
    
    // Main class to run the program
    public class AbstractClassDemo {
        public static void main(String[] args) {
            Animal myDog = new Dog();  // Animal reference, Dog object
            myDog.eat();               // Calls concrete method from abstract class
            myDog.sound();             // Calls overridden method
        }
    }
    

    Output:

    This animal eats food
    Dog barks
    

    Key Points About Abstract Class

    FeatureDescription
    InstantiationCannot create object of abstract class directly
    Abstract MethodsDeclared with abstract keyword; No method body
    Concrete MethodsCan have implemented methods
    ConstructorAbstract classes can have constructors
    VariablesCan have instance variables
    Access ModifiersAbstract methods can have public or protected access modifiers
    Subclass ResponsibilityConcrete subclass must implement all abstract methods or itself be abstract

    Abstract Class vs Interface

    Abstract ClassInterface
    Can have constructorsNo constructors (before Java 8)
    Can have abstract + concrete methodsJava 8+: Can have default methods with implementation
    Can maintain state (instance variables)No instance variables (except static and final)
    Supports method access modifiers (private/protected/public)All methods are public (by default)
    Used when classes are closely relatedUsed for defining capabilities or behavior across unrelated classes

    When to Use Abstract Class?

    • Use an abstract class when a base class with common functionality should be shared by multiple subclasses.
    • It is helpful in cases where partial implementation is needed.
    • Abstract classes also improve code reusability while enforcing implementation rules in subclasses.

      Example of Abstract Class with Multiple Subclasses

      abstract class Shape {
          abstract void area();
          
          void display() {
              System.out.println("This is a shape");
          }
      }
      
      class Circle extends Shape {
          int radius = 5;
          
          void area() {
              System.out.println("Area of Circle: " + (3.14 * radius * radius));
          }
      }
      
      class Rectangle extends Shape {
          int length = 10, width = 5;
          
          void area() {
              System.out.println("Area of Rectangle: " + (length * width));
          }
      }
      
      public class ShapeDemo {
          public static void main(String[] args) {
              Shape c = new Circle();
              c.display();
              c.area();
              
              Shape r = new Rectangle();
              r.display();
              r.area();
          }
      }
      

      Output:

      This is a shape
      Area of Circle: 78.5
      This is a shape
      Area of Rectangle: 50
      

      Best Practices

      • Prefer abstract classes when classes share a strong relationship and common implementation.
      • Avoid creating large abstract classes with too many abstract methods.
      • Keep it clean: Abstract class should focus on providing a template and common functionality.

      Summary

      An abstract class in Java provides a blueprint for subclasses. It helps enforce rules by using abstract methods, while at the same time allowing code reuse through concrete methods. Moreover, it serves as a powerful tool when designing applications that follow key OOP principles, such as abstraction, inheritance, and polymorphism. In addition, abstract classes promote a clean and scalable architecture by clearly defining a template for subclasses.

    • Java Interface Tutorial – A Complete Guide

      In Java, an interface is a blueprint for a class. It is a reference type, similar to a class, and it can contain abstract methods, default methods, static methods, and constants. Interfaces are used to achieve abstraction and multiple inheritance in Java.

      1. What is an Interface in Java?

      An interface is a collection of abstract methods and constants. Unlike classes, interfaces cannot be instantiated. Classes implement interfaces to provide concrete behavior.

      Key points:

      • Defines what a class should do, not how it does it.
      • Promotes loose coupling between classes.
      • Supports multiple inheritance (a class can implement multiple interfaces).

      2. Syntax of an Interface

      interface InterfaceName {
          // constant declaration
          int MAX_VALUE = 100;  // public, static, final by default
      
          // abstract method
          void method1();       // public and abstract by default
      
          // default method (since Java 8)
          default void defaultMethod() {
              System.out.println("This is a default method.");
          }
      
          // static method (since Java 8)
          static void staticMethod() {
              System.out.println("This is a static method.");
          }
      }
      

      3. Implementing an Interface

      A class implements an interface using the implements keyword. It must override all abstract methods of the interface.

      Example:

      interface Vehicle {
          void start();
          void stop();
      }
      
      class Car implements Vehicle {
          @Override
          public void start() {
              System.out.println("Car is starting...");
          }
      
          @Override
          public void stop() {
              System.out.println("Car is stopping...");
          }
      }
      
      public class InterfaceDemo {
          public static void main(String[] args) {
              Vehicle myCar = new Car();
              myCar.start();
              myCar.stop();
          }
      }
      

      Output:

      Car is starting...
      Car is stopping...
      

      4. Multiple Interface Implementation

      Java allows a class to implement multiple interfaces, which is a way to achieve multiple inheritance.

      interface Engine {
          void startEngine();
      }
      
      interface GPS {
          void navigate();
      }
      
      class SmartCar implements Engine, GPS {
          @Override
          public void startEngine() {
              System.out.println("Engine started!");
          }
      
          @Override
          public void navigate() {
              System.out.println("Navigating to destination...");
          }
      }
      
      public class MultipleInterfaceDemo {
          public static void main(String[] args) {
              SmartCar car = new SmartCar();
              car.startEngine();
              car.navigate();
          }
      }
      

      Output:

      Engine started!
      Navigating to destination...
      

      5. Default and Static Methods in Interface

      • Default Methods: Allow interfaces to have method implementations.
      • Static Methods: Belong to the interface, not to the object.
      interface Printer {
          void print();
      
          default void printPreview() {
              System.out.println("Printing preview...");
          }
      
          static void staticMethod() {
              System.out.println("Static method in interface.");
          }
      }
      
      class DocumentPrinter implements Printer {
          @Override
          public void print() {
              System.out.println("Printing document...");
          }
      }
      
      public class DefaultStaticDemo {
          public static void main(String[] args) {
              DocumentPrinter dp = new DocumentPrinter();
              dp.print();
              dp.printPreview();       // default method
              Printer.staticMethod();  // static method
          }
      }
      

      Output:

      Printing document...
      Printing preview...
      Static method in interface.
      

      6. Key Features of Interfaces

      FeatureDescription
      Abstract MethodsMethods without body (implemented by class).
      Default MethodsMethods with default implementation.
      Static MethodsBelongs to interface, not objects.
      ConstantsVariables are public, static, final by default.
      Multiple InheritanceA class can implement multiple interfaces.

      7. Practical Example: Real-World Scenario

      Imagine an online payment system. Different payment methods (CreditCard, PayPal) can implement the Payment interface.

      interface Payment {
          void pay(double amount);
      }
      
      class CreditCardPayment implements Payment {
          @Override
          public void pay(double amount) {
              System.out.println("Paid " + amount + " using Credit Card.");
          }
      }
      
      class PayPalPayment implements Payment {
          @Override
          public void pay(double amount) {
              System.out.println("Paid " + amount + " using PayPal.");
          }
      }
      
      public class PaymentDemo {
          public static void main(String[] args) {
              Payment payment1 = new CreditCardPayment();
              Payment payment2 = new PayPalPayment();
      
              payment1.pay(500);
              payment2.pay(1000);
          }
      }
      

      Output:

      Paid 500.0 using Credit Card.
      Paid 1000.0 using PayPal.
      

      8. Interface vs Abstract Class

      AspectInterfaceAbstract Class
      MethodsAbstract by default, can have default & static methodsCan have abstract & concrete methods
      Variablespublic, static, finalAny access modifier, non-final allowed
      InheritanceMultiple interfaces allowedOnly single inheritance
      Use caseDefine contract for unrelated classesShare code between closely related classes

      9. Best Practices

      1. Use interfaces to define contracts for classes.
      2. Prefer interfaces over abstract classes when you need multiple inheritance.
      3. Keep interfaces focused: one interface = one responsibility.
      4. Use default methods sparingly; they are mostly for backward compatibility.

      Conclusion:


      Interfaces are fundamental in Java for abstraction, multiple inheritance, and loose coupling. Understanding them helps in designing flexible and maintainable systems.

    • Ways to Create an Object in Java

      Creating objects in Java is a fundamental concept in Object-Oriented Programming (OOP). In Java, there are multiple ways to create an object, each having its specific use case, advantages, and implications. This tutorial explains all possible ways in detail with scenarios and examples.

      ✅ 1. Using new Keyword (Most Common Approach)

      How It Works:

      • The new keyword allocates memory for the new object and calls the constructor.

      Example:

      public class Student {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s1 = new Student("Alice", 20);
              System.out.println(s1.name + " - " + s1.age);
          }
      }
      

      Scenario:

      Use this approach for general purpose object creation when you want a fresh object.

      ✅ 2. Using Class.forName() and newInstance() (Reflection)

      How It Works:

      • The class name is passed as a string, and the object is created at runtime.
      • Suitable for dynamic object creation.

      Example:

      public class Student {
          public void display() {
              System.out.println("Student Object Created using Reflection");
          }
      }
      
      public class Main {
          public static void main(String[] args) throws Exception {
              Class<?> cls = Class.forName("Student");
              Student s = (Student) cls.getDeclaredConstructor().newInstance();
              s.display();
          }
      }
      

      Scenario:

      Useful when the class name is known only at runtime (e.g., in plugin architectures).

      ✅ 3. Using clone() Method (Object Cloning)

      How It Works:

      • Creates a copy of an existing object.
      • Class must implement Cloneable interface and override clone().

      Example:

      public class Student implements Cloneable {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          @Override
          protected Object clone() throws CloneNotSupportedException {
              return super.clone();
          }
      }
      
      public class Main {
          public static void main(String[] args) throws CloneNotSupportedException {
              Student s1 = new Student("Bob", 22);
              Student s2 = (Student) s1.clone();
              System.out.println(s2.name + " - " + s2.age);
          }
      }
      

      Scenario:

      Useful when you want to create a duplicate object without invoking the constructor.

      ✅ 4. Using Deserialization

      How It Works:

      • Converts a byte stream back into an object.
      • Useful for persisting and transferring objects.

      Example:

      import java.io.*;
      
      public class Student implements Serializable {
          String name;
          int age;
      
          public Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      
      public class Main {
          public static void main(String[] args) throws Exception {
              // Serialize
              Student s1 = new Student("Charlie", 24);
              ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.ser"));
              out.writeObject(s1);
              out.close();
      
              // Deserialize
              ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.ser"));
              Student s2 = (Student) in.readObject();
              in.close();
      
              System.out.println(s2.name + " - " + s2.age);
          }
      }
      

      Scenario:

      Ideal for restoring objects from storage or transferring them across networks.

      ✅ 5. Using Factory Method (Design Pattern)

      How It Works:

      • Factory methods provide a controlled way to create objects.

      Example:

      class Student {
          private String name;
          private int age;
      
          private Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public static Student createStudent(String name, int age) {
              return new Student(name, age);
          }
      
          public void display() {
              System.out.println(name + " - " + age);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s = Student.createStudent("David", 25);
              s.display();
          }
      }
      

      Scenario:

      Recommended when object creation logic is complex or needs encapsulation.

      ✅ 6. Using Builder Pattern

      How It Works:

      • Provides flexibility in object creation with multiple optional parameters.

      Example:

      public class Student {
          private String name;
          private int age;
          private String course;
      
          private Student(StudentBuilder builder) {
              this.name = builder.name;
              this.age = builder.age;
              this.course = builder.course;
          }
      
          public static class StudentBuilder {
              private String name;
              private int age;
              private String course;
      
              public StudentBuilder setName(String name) {
                  this.name = name;
                  return this;
              }
      
              public StudentBuilder setAge(int age) {
                  this.age = age;
                  return this;
              }
      
              public StudentBuilder setCourse(String course) {
                  this.course = course;
                  return this;
              }
      
              public Student build() {
                  return new Student(this);
              }
          }
      
          public void display() {
              System.out.println(name + ", " + age + " years, Course: " + course);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Student s = new Student.StudentBuilder()
                  .setName("Eve")
                  .setAge(23)
                  .setCourse("Computer Science")
                  .build();
              s.display();
          }
      }
      

      Scenario:

      Best for creating objects with many optional fields.

      ✅ Summary of All Ways

      MethodScenario Use Case
      new KeywordSimple, standard object creation
      ReflectionDynamic class loading and instantiation
      clone()Object duplication without calling constructor
      DeserializationObject persistence and transfer
      Factory MethodEncapsulated creation logic
      Builder PatternComplex object creation with optional fields

      🎯 Conclusion

      Understanding various ways of object creation allows developers to choose the most suitable method depending on the scenario. Whether for simplicity, performance, or flexibility, each method has its advantages.

    • Abstraction in Java :-A Complete Guide

      1. What is Abstraction?

      Abstraction is one of the core OOP concepts. It is the process of hiding the internal details of how something works and only exposing the essential features or behaviors to the user.

      • You don’t show the “how”, only the “what it does”.
      • It allows the user to use functionality without worrying about internal implementation.

      In Java, abstraction is achieved by:

      1. Abstract Classes
      2. Interfaces

      2. How Abstraction Works

      Key Points:

      1. Hide Implementation: Users of a class don’t need to know the inner details of methods. They just call the method.
      2. Only Show Behavior: You provide a method signature (name, input/output), but the internal logic can be hidden.
      3. Restrict Direct Access: Internal variables can be private so they cannot be accessed directly, only through methods.

      Example Using Abstract Class

      // Abstract class
      abstract class Vehicle {
          // Abstract method (no implementation)
          abstract void startEngine();
      
          // Regular method (implementation can be provided)
          void fuelType() {
              System.out.println("Fuel type is Petrol/Diesel/Electric");
          }
      }
      
      // Child class provides implementation
      class Car extends Vehicle {
          @Override
          void startEngine() {
              System.out.println("Car engine starts with a key or button");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Vehicle myCar = new Car();  // Reference type is abstract class
              myCar.startEngine();        // Calls implemented method in Car
              myCar.fuelType();           // Calls inherited method
          }
      }
      

      Explanation:

      • Vehicle class hides how the engine starts.
      • Car class provides the implementation of startEngine().
      • The user only interacts with the startEngine() method without knowing internal details.
      • This is abstraction in action.

      Example Using Interface

      interface RemoteControl {
          void turnOn();
          void turnOff();
      }
      
      class TV implements RemoteControl {
          @Override
          public void turnOn() {
              System.out.println("TV is turned ON");
          }
      
          @Override
          public void turnOff() {
              System.out.println("TV is turned OFF");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              RemoteControl myTV = new TV();
              myTV.turnOn();   // Only uses the method
              myTV.turnOff();  // No idea how the internal logic works
          }
      }
      

      Explanation:

      • RemoteControl interface only defines what actions are possible.
      • TV class defines how those actions are executed.
      • The user is only aware of what methods they can call.

      3. Restricting Data and Only Showing Functions

      • Use private variables to hide data.
      • Provide public methods (getter/setter) to access the data.
      • This prevents direct modification and ensures controlled access.
      class BankAccount {
          private double balance;  // Hidden from the user
      
          // Method to deposit money (control access)
          public void deposit(double amount) {
              if (amount > 0) {
                  balance += amount;
                  System.out.println("Deposited: " + amount);
              }
          }
      
          // Method to check balance
          public double getBalance() {
              return balance;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              BankAccount account = new BankAccount();
              account.deposit(500);
              System.out.println("Balance: " + account.getBalance());
          }
      }
      
      • User cannot directly access balance.
      • User can only interact through methods, which is controlled abstraction.

      4. Summary of Abstraction

      FeatureDescription
      PurposeHide implementation, show only functionality
      How to implementAbstract classes or interfaces
      BenefitsSimplifies complexity, improves security, allows flexibility
      Restrict dataUse private variables + public methods
      Real-life analogyDriving a car: you know how to drive it, but not how the engine works

      Key Idea: Abstraction is about hiding the “how” and showing the “what”. It protects your data and makes code easier to use and maintain.

    • Encapsulation in Java: – A Complete Tutorial

      1. What is Encapsulation?

      Encapsulation is one of the four core OOP concepts (along with Inheritance, Polymorphism, and Abstraction).

      It is the mechanism of restricting direct access to some of an object’s components and providing controlled access through methods.

      Key points:

      • Data (fields) of a class are made private.
      • Access to these fields is provided via public getter and setter methods.
      • This helps control how the data is accessed or modified, improving security and maintainability.

      2. Why use Encapsulation?

      Encapsulation provides several benefits:

      1. Data Hiding – Prevents external classes from directly modifying sensitive data.
      2. Control Access – You can validate inputs before modifying a variable.
      3. Flexibility – Internal implementation can change without affecting external code.
      4. Improved Maintainability – Changes in one class do not affect others if encapsulation is properly used.

      3. How Encapsulation Works

      Encapsulation works by:

      1. Declaring class variables as private.
      2. Providing public getter and setter methods to access or modify these variables.
      3. Optionally, applying logic in setters to validate or restrict data.

      4. Example: Encapsulation in a POJO

      Here’s a simple example of a POJO with encapsulation:

      public class Student {
          // Step 1: Make fields private
          private String name;
          private int age;
      
          // Step 2: Provide public getters
          public String getName() {
              return name;
          }
      
          public int getAge() {
              return age;
          }
      
          // Step 3: Provide public setters with validation
          public void setName(String name) {
              if (name != null && name.length() > 0) {
                  this.name = name;
              } else {
                  System.out.println("Invalid name.");
              }
          }
      
          public void setAge(int age) {
              if (age > 0) {
                  this.age = age;
              } else {
                  System.out.println("Age must be positive.");
              }
          }
      }
      

      5. Using the Encapsulated Class

      public class Main {
          public static void main(String[] args) {
              Student student = new Student();
      
              // Trying to directly access the fields (won't work)
              // student.name = "John"; // ERROR: name has private access
      
              // Using setter methods
              student.setName("John");  // Allowed
              student.setAge(25);       // Allowed
              student.setAge(-5);       // Rejected due to validation
      
              // Using getter methods
              System.out.println("Student Name: " + student.getName());
              System.out.println("Student Age: " + student.getAge());
          }
      }
      

      Output:

      Age must be positive.
      Student Name: John
      Student Age: 25
      

      6. How it Restricts Data

      • Direct modification is blocked: private keyword prevents other classes from accessing the variables.
      • Controlled modification: The setter validates the data before setting it.
      • Read-only or write-only access: You can provide only getter (read-only) or setter (write-only) if needed.

      Example: Read-only field

      private final String studentId; // Cannot be changed once assigned
      
      public String getStudentId() {
          return studentId;
      }
      
      // No setter method, so it’s read-only
      

      7. Real-world analogy

      Think of encapsulation like a bank account:

      • Your balance is private.
      • You can’t just change it directly.
      • You deposit or withdraw through controlled methods.
      • The bank validates your transactions before updating your balance.

    • Method Overloading in Java – A Complete Guide

      Introduction

      Method Overloading is a feature in Java that allows a class to have more than one method with the same name, but with different parameter lists. It is a part of compile-time polymorphism or static polymorphism. Method overloading improves code readability and usability.


      1. Key Rules for Method Overloading

      1. Method name must be same.
      2. Parameter list must be different:
        • Different number of parameters
        • Different data types of parameters
        • Different sequence of parameters
      3. Return type can be different, but it alone cannot distinguish overloaded methods.
      4. Access modifiers can differ.
      5. Overloaded methods can throw different exceptions.

      2. Scenarios for Method Overloading

      Let’s discuss all possible scenarios with examples.

      2.1. Overloading by Number of Parameters

      class Calculator {
          // Method with 2 parameters
          int add(int a, int b) {
              return a + b;
          }
      
          // Method with 3 parameters
          int add(int a, int b, int c) {
              return a + b + c;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Calculator calc = new Calculator();
              System.out.println(calc.add(10, 20));       // Output: 30
              System.out.println(calc.add(10, 20, 30));   // Output: 60
          }
      }
      

      Explanation:
      Java determines which method to call based on the number of arguments.

      2.2. Overloading by Data Type of Parameters

      class Calculator {
          int multiply(int a, int b) {
              return a * b;
          }
      
          double multiply(double a, double b) {
              return a * b;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Calculator calc = new Calculator();
              System.out.println(calc.multiply(5, 10));      // Output: 50
              System.out.println(calc.multiply(5.5, 2.0));   // Output: 11.0
          }
      }
      

      Explanation:
      The compiler chooses the method based on parameter type matching.

      2.3. Overloading by Sequence of Parameters

      class Calculator {
          void display(int a, double b) {
              System.out.println("int-double: " + a + ", " + b);
          }
      
          void display(double a, int b) {
              System.out.println("double-int: " + a + ", " + b);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Calculator calc = new Calculator();
              calc.display(10, 5.5);  // Output: int-double: 10, 5.5
              calc.display(5.5, 10);  // Output: double-int: 5.5, 10
          }
      }
      

      Explanation:
      Even with the same number and type of parameters, order matters.

      2.4. Overloading with Varargs

      class Calculator {
          int sum(int... numbers) {
              int total = 0;
              for (int n : numbers) total += n;
              return total;
          }
      
          int sum(int a, int b) {
              return a + b;
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Calculator calc = new Calculator();
              System.out.println(calc.sum(10, 20));       // Calls sum(int a, int b) → 30
              System.out.println(calc.sum(10, 20, 30));   // Calls sum(int... numbers) → 60
          }
      }
      

      Explanation:
      Varargs methods can accept any number of arguments, but the compiler prefers exact match first.

      2.5. Overloading with Different Return Types (Not Alone)

      class Test {
          int compute(int a, int b) {
              return a + b;
          }
      
          // int compute(int a, int b) { return a * b; } // ❌ Not allowed
      
          double compute(int a, double b) {
              return a * b;
          }
      }
      

      Explanation:
      Java cannot differentiate methods by return type alone. You must change the parameters to overload.

      2.6. Overloading with Access Modifiers & Exceptions

      class Example {
          public void show(int a) {
              System.out.println("Public method: " + a);
          }
      
          private void show(int a, int b) {
              System.out.println("Private method: " + (a + b));
          }
      
          void show(String s) throws Exception {
              System.out.println("Throws exception: " + s);
          }
      }
      

      Explanation:
      Access modifiers and exceptions do not affect overloading. Only the parameter list matters.

      2.7. Real-Time Example – Online Shopping

      class ShoppingCart {
          void addItem(String item) {
              System.out.println(item + " added to cart.");
          }
      
          void addItem(String item, int quantity) {
              System.out.println(quantity + " " + item + "(s) added to cart.");
          }
      
          void addItem(String item, double price) {
              System.out.println(item + " added with price $" + price);
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              ShoppingCart cart = new ShoppingCart();
              cart.addItem("Book");               // Single item
              cart.addItem("Pen", 10);            // Multiple quantity
              cart.addItem("Laptop", 1500.50);    // With price
          }
      }
      

      Explanation:
      Overloading provides a flexible API for different input scenarios.

      3. Points to Remember

      1. Method overloading is resolved at compile time (Static Polymorphism).
      2. Cannot overload methods only by return type.
      3. Can overload constructors as well.
      4. Can combine varargs, different types, and number of parameters for flexible API design.

      4. Overloading vs Overriding

      FeatureOverloadingOverriding
      Occurs inSame classSubclass
      Method nameSameSame
      ParameterMust differMust be same
      Return typeCan differ (but not alone)Must be same or covariant
      Compile/RuntimeCompile timeRuntime
      InheritanceNot requiredRequired

      5. Conclusion

      Method overloading in Java enhances code readability and flexibility, allowing the same method to handle different types or numbers of inputs. It is a core part of compile-time polymorphism in object-oriented programming.

    • Java Method Overriding : Complete Guide with Examples

      Method overriding is a core concept in Java, allowing a subclass to provide a specific implementation for a method already defined in its superclass. It plays a crucial role in achieving runtime polymorphism.


      1. What is Method Overriding?

      Method overriding occurs when a subclass defines a method with the same name, return type, and parameters as a method in its superclass.

      Key Points:

      • Overridden method must have same name, return type, and parameters.
      • Access level cannot be more restrictive than the superclass method.
      • Only instance methods can be overridden (static, final, and private methods cannot be overridden).
      • Supports runtime polymorphism.

      2. Method Overriding Syntax

      class SuperClass {
          void display() {
              System.out.println("Display from SuperClass");
          }
      }
      
      class SubClass extends SuperClass {
          @Override
          void display() {
              System.out.println("Display from SubClass");
          }
      }
      
      public class Test {
          public static void main(String[] args) {
              SuperClass obj = new SubClass();
              obj.display();  // Calls SubClass's display()
          }
      }
      

      Explanation:

      • @Override is optional but recommended. It ensures you are actually overriding a method.
      • The method in SubClass replaces the SuperClass version when called on a subclass object.

      3. Rules of Method Overriding

      RuleExplanationExample
      Method signature must be sameName + parameters must matchvoid display() cannot override void show()
      Return type must be compatibleCan be same or a subtype (covariant return type)String getName() in subclass can override Object getName() in superclass
      Access levelCannot be more restrictiveprotected in superclass → public in subclass ✅; privateprotected
      Final method cannot be overriddenfinal void print() cannot be overriddenCompile-time error
      Static methods cannot be overriddenStatic methods are hidden, not overriddenstatic void show() in subclass hides superclass method
      Private methods cannot be overriddenThey are not inheritedprivate void msg() cannot be overridden
      Constructor cannot be overriddenConstructors are not inherited

      4. Covariant Return Type

      Java allows overriding methods to return a subclass type of the original method’s return type.

      class SuperClass {
          SuperClass getInstance() {
              return new SuperClass();
          }
      }
      
      class SubClass extends SuperClass {
          @Override
          SubClass getInstance() {
              return new SubClass();
          }
      }
      

      Explanation:
      SubClass getInstance() overrides SuperClass getInstance() because SubClass is a subtype of SuperClass.

      5. Access Modifier Scenarios

      • Superclass method is private → cannot override.
      • Superclass method is default/package-private → can override in the same package.
      • Superclass method is protected → can override with protected or public.
      • Superclass method is public → can override only with public.

      6. Final Method Scenario

      class SuperClass {
          final void show() {
              System.out.println("Final method");
          }
      }
      
      class SubClass extends SuperClass {
          // void show() { } // ❌ Compile-time error
      }
      

      Explanation: Final methods cannot be overridden.

      7. Static Method Hiding

      Static methods cannot be overridden; they are hidden.

      class SuperClass {
          static void greet() {
              System.out.println("Hello from SuperClass");
          }
      }
      
      class SubClass extends SuperClass {
          static void greet() {
              System.out.println("Hello from SubClass");
          }
      }
      
      public class Test {
          public static void main(String[] args) {
              SuperClass.greet(); // SuperClass version
              SubClass.greet();   // SubClass version
          }
      }
      

      Explanation: This is method hiding, not overriding. The version called depends on the class reference, not the object.

      8. Real-Time Example: Overriding in a Banking System

      class Bank {
          double getInterestRate() {
              return 5.0;
          }
      }
      
      class SBI extends Bank {
          @Override
          double getInterestRate() {
              return 6.5;
          }
      }
      
      class ICICI extends Bank {
          @Override
          double getInterestRate() {
              return 7.0;
          }
      }
      
      public class TestBank {
          public static void main(String[] args) {
              Bank b1 = new SBI();
              Bank b2 = new ICICI();
              System.out.println("SBI Interest: " + b1.getInterestRate());
              System.out.println("ICICI Interest: " + b2.getInterestRate());
          }
      }
      

      Explanation:

      • Runtime polymorphism allows different bank types to provide their own interest rates.
      • The same method getInterestRate() behaves differently depending on the subclass object.

      9. Super Keyword Usage in Overriding

      You can call the parent class method from the subclass using super.

      class SuperClass {
          void display() {
              System.out.println("SuperClass method");
          }
      }
      
      class SubClass extends SuperClass {
          @Override
          void display() {
              super.display(); // Calls SuperClass method
              System.out.println("SubClass method");
          }
      }
      

      Output:

      SuperClass method
      SubClass method
      

      10. Summary

      • Overriding allows dynamic polymorphism.
      • Must follow method signature, access modifier, and return type rules.
      • final, static, private methods cannot be overridden.
      • super keyword helps access parent class methods.
      • Real-time use: frameworks (Spring, Hibernate), banking systems, UI, and game engines.

      💡 Tip for Readers:
      Always use @Override annotation. It helps avoid errors like accidental overloading instead of overriding.

    • Polymorphism in Java

      Polymorphism in Java

      Definition

      Polymorphism is an Object-Oriented Programming (OOP) concept that allows objects to take multiple forms. The word “polymorphism” comes from Greek: “poly” (many) + “morph” (forms).

      In Java, polymorphism allows:

      1. One interface, multiple implementations
      2. Methods to behave differently based on the object that invokes them

      It provides flexibility and reusability in code.


      Types of Polymorphism in Java

      Polymorphism in Java is of two types:

      1. Compile-time Polymorphism (Static Polymorphism)
      2. Runtime Polymorphism (Dynamic Polymorphism)

      1. Compile-time Polymorphism (Method Overloading)

      Occurs when multiple methods in the same class have the same name but different parameters (number or type).

      Example:

      class Calculator {
      
          // Method to add two integers
          int add(int a, int b) {
              return a + b;
          }
      
          // Method to add three integers
          int add(int a, int b, int c) {
              return a + b + c;
          }
      
          // Method to add two double numbers
          double add(double a, double b) {
              return a + b;
          }
      }
      
      public class TestPolymorphism {
          public static void main(String[] args) {
              Calculator calc = new Calculator();
      
              System.out.println(calc.add(5, 10));        // Calls add(int, int)
              System.out.println(calc.add(5, 10, 15));    // Calls add(int, int, int)
              System.out.println(calc.add(5.5, 10.5));    // Calls add(double, double)
          }
      }
      

      Explanation:
      The method add behaves differently depending on the arguments passed. This is compile-time polymorphism, decided during compilation.

      2. Runtime Polymorphism (Method Overriding)

      Occurs when a subclass provides a specific implementation of a method that is already defined in its superclass.
      The method to call is determined at runtime based on the actual object type.

      Real-world analogy:

      Think of a Bird class. All birds can makeSound(). But a Sparrow chirps and a Parrot talks. The same method name produces different behaviors depending on the bird.

      Example:

      // Superclass
      class Bird {
          void makeSound() {
              System.out.println("Some generic bird sound");
          }
      }
      
      // Subclass 1
      class Sparrow extends Bird {
          @Override
          void makeSound() {
              System.out.println("Chirp chirp");
          }
      }
      
      // Subclass 2
      class Parrot extends Bird {
          @Override
          void makeSound() {
              System.out.println("Squawk! Hello!");
          }
      }
      
      public class TestPolymorphism {
          public static void main(String[] args) {
              Bird myBird1 = new Sparrow();
              Bird myBird2 = new Parrot();
      
              myBird1.makeSound();   // Output: Chirp chirp
              myBird2.makeSound();   // Output: Squawk! Hello!
          }
      }
      

      Explanation:
      Even though both myBird1 and myBird2 are declared as type Bird, the actual object type (Sparrow or Parrot) determines which makeSound() method is called. This is runtime polymorphism.


      Benefits of Polymorphism

      1. Code reusability – Write one interface and reuse it with different objects.
      2. Flexibility – New classes can be introduced without changing existing code.
      3. Maintainability – Less coupling between classes.
      4. Simplifies code – You can write generic code that works with different object types.

      Key Points to Mention in a Blog Tutorial

      • Polymorphism allows a single method name to perform different tasks.
      • Compile-time polymorphism = method overloading (decided at compile-time).
      • Runtime polymorphism = method overriding (decided at runtime).
      • Always involves inheritance and interfaces in runtime polymorphism.
      • Real-world analogy makes understanding easier (like birds making different sounds).
    • Inheritance in Java

      1. What is Inheritance in Java?

      Inheritance is an Object-Oriented Programming (OOP) concept where a class (child/subclass) acquires the properties and behaviors (fields and methods) of another class (parent/superclass).

      Purpose:

      • Reusability: Avoids rewriting code.
      • Extensibility: Add new features easily.
      • Polymorphism Support: Enables overriding methods.

      Syntax:

      class Parent {
          int a = 10;
          void display() {
              System.out.println("Parent method");
          }
      }
      
      class Child extends Parent {
          void show() {
              System.out.println("Child method");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Child c = new Child();
              c.display(); // inherited from Parent
              c.show();    // Child's own method
          }
      }
      

      2. Types of Inheritance in Java

      Java supports multiple types of inheritance except multiple inheritance using classes (to avoid ambiguity, e.g., Diamond problem).

      A. Single Inheritance

      • Definition: A child class inherits from one parent class.
      • Example:
      class Vehicle {
          void start() { System.out.println("Vehicle started"); }
      }
      
      class Car extends Vehicle {
          void honk() { System.out.println("Car honks"); }
      }
      
      public class Main {
          public static void main(String[] args) {
              Car c = new Car();
              c.start();
              c.honk();
          }
      }
      
      • Real-time Example: Car is a Vehicle. Car inherits features of Vehicle (start engine, fuel system).

      Diagram:

      Vehicle
         |
         V
        Car
      

      B. Multilevel Inheritance

      • Definition: A class inherits from a class, which in turn inherits from another class.
      • Example:
      class Animal {
          void eat() { System.out.println("Animal eats"); }
      }
      
      class Mammal extends Animal {
          void walk() { System.out.println("Mammal walks"); }
      }
      
      class Dog extends Mammal {
          void bark() { System.out.println("Dog barks"); }
      }
      
      public class Main {
          public static void main(String[] args) {
              Dog d = new Dog();
              d.eat();
              d.walk();
              d.bark();
          }
      }
      
      • Real-time Example: Dog is a Mammal, Mammal is an Animal. So Dog inherits features of Mammal and Animal.

      Diagram:

      Animal
         |
      Mammal
         |
        Dog
      

      C. Hierarchical Inheritance

      • Definition: Multiple classes inherit from one parent class.
      • Example:
      class Vehicle {
          void start() { System.out.println("Vehicle starts"); }
      }
      
      class Car extends Vehicle {
          void honk() { System.out.println("Car honks"); }
      }
      
      class Bike extends Vehicle {
          void kick() { System.out.println("Bike kicks"); }
      }
      
      public class Main {
          public static void main(String[] args) {
              Car c = new Car();
              c.start();
              c.honk();
      
              Bike b = new Bike();
              b.start();
              b.kick();
          }
      }
      
      • Real-time Example: Car and Bike are Vehicles. Both inherit Vehicle’s start() feature.

      Diagram:

              Vehicle
             /      \
           Car      Bike
      

      D. Multiple Inheritance (Through Interfaces)

      • Note: Java does not allow multiple inheritance with classes to avoid ambiguity (diamond problem). But we can achieve it using interfaces.
      • Example:
      interface Engine {
          void engineStart();
      }
      
      interface Fuel {
          void fuelType();
      }
      
      class Car implements Engine, Fuel {
          public void engineStart() { System.out.println("Engine starts"); }
          public void fuelType() { System.out.println("Uses petrol"); }
      }
      
      public class Main {
          public static void main(String[] args) {
              Car c = new Car();
              c.engineStart();
              c.fuelType();
          }
      }
      
      • Real-time Example: Car can have features of both Engine and Fuel.

      Diagram:

      Engine       Fuel
         \         /
             Car
      

      E. Hybrid Inheritance

      • Definition: Combination of two or more inheritance types. Java implements it via classes + interfaces.
      • Example:
      interface A {
          void methodA();
      }
      
      class B {
          void methodB() { System.out.println("Method B"); }
      }
      
      class C extends B implements A {
          public void methodA() { System.out.println("Method A"); }
      }
      
      public class Main {
          public static void main(String[] args) {
              C obj = new C();
              obj.methodA();
              obj.methodB();
          }
      }
      
      • Real-time Example: C class inherits B (single) and A (interface), combining behaviors.

      Diagram:

         A(interface)       B(class)
              \            /
                   C
      

      3. Key Points / Disclosures

      1. Java supports: Single, Multilevel, Hierarchical, Hybrid (with interfaces), Multiple (via interfaces).
      2. Java does not support: Multiple inheritance with classes.
      3. super keyword: Refers to parent class object. Can call parent constructor or method.
      4. Constructor chaining: Parent class constructor is called first.
      5. Access Modifiers Impact: Private members of parent class cannot be inherited, but protected and public can be.

      Example of super:

      class Parent {
          Parent() { System.out.println("Parent constructor"); }
      }
      
      class Child extends Parent {
          Child() {
              super(); // Calls Parent constructor
              System.out.println("Child constructor");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Child c = new Child();
          }
      }
      

      Output:

      Parent constructor
      Child constructor
      

      Summary Table:

      TypeExampleReal-time Use Case
      SingleCar extends VehicleCar is a Vehicle
      MultilevelDog -> Mammal -> AnimalDog inherits Mammal & Animal features
      HierarchicalCar, Bike -> VehicleCar & Bike share Vehicle features
      Multiple (interface)Car implements Engine,FuelCar uses Engine & Fuel features
      HybridC extends B implements ACombines class + interface features