• 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 βœ…; private β†’ protected ❌
    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

  • Introduction to OOPs Concepts in Java

    Introduction to OOPs Concepts in Java

    Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to model real-world entities. Java is a purely object-oriented language (except for primitive types), making it ideal for understanding OOPs concepts.

    The main principles of OOPs in Java are:

    1. Class and Object
    2. Encapsulation
    3. Inheritance
    4. Polymorphism
    5. Abstraction

    We’ll discuss each principle in detail with examples.


    1. Class and Object

    Class is a blueprint for creating objects. It defines properties (fields) and behaviors (methods) of an object.

    Object is an instance of a class. Each object has its own state and behavior.

    Example:

    // Class definition
    class Car {
        String color;
        String model;
    
        void displayDetails() {
            System.out.println("Car model: " + model + ", Color: " + color);
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            // Object creation
            Car car1 = new Car();
            car1.color = "Red";
            car1.model = "Toyota";
    
            Car car2 = new Car();
            car2.color = "Blue";
            car2.model = "Honda";
    
            car1.displayDetails();
            car2.displayDetails();
        }
    }
    

    Explanation:

    • Car is a class.
    • car1 and car2 are objects of the Car class.
    • Each object has its own values for color and model.

    πŸ‘‰ To learn more about Objects and Classes in Java with examples, visit this detailed guide on Object and Class in Java.

    2. Encapsulation

    Encapsulation is the wrapping of data (variables) and code (methods) together as a single unit. It also restricts direct access to some of the object’s components, making the class more secure.

    • Access Modifiers like private, public, and protected control access.
    • Getters and Setters are used to access private variables.

    Example:

    class Person {
        private String name;
        private int age;
    
        // Getter for name
        public String getName() {
            return name;
        }
    
        // Setter for name
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            if(age > 0) {
                this.age = age;
            } else {
                System.out.println("Age must be positive");
            }
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Person person = new Person();
            person.setName("Alice");
            person.setAge(25);
    
            System.out.println("Name: " + person.getName());
            System.out.println("Age: " + person.getAge());
        }
    }
    

    Explanation:

    • The Person class uses private fields to restrict direct access.
    • Getters and setters allow controlled access to these fields.

    πŸ‘‰ To explore Encapsulation in detail with examples, check out this guide on Encapsulation in Java.

    3. Inheritance

    Inheritance allows a class to inherit properties and methods from another class, promoting code reusability.

    • Super Class (Parent class) – The class whose features are inherited.
    • Sub Class (Child class) – The class that inherits features.

    Example:

    // Parent class
    class Animal {
        void eat() {
            System.out.println("This animal eats food.");
        }
    }
    
    // Child class
    class Dog extends Animal {
        void bark() {
            System.out.println("Dog barks");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.eat(); // inherited from Animal
            dog.bark(); // own method
        }
    }
    

    Explanation:

    • Dog inherits the eat() method from Animal.
    • This demonstrates code reuse.

    πŸ‘‰ To explore Inheritance in detail with examples, check out this guide on Inheritence in Java.

    4. Polymorphism

    Polymorphism means β€œmany forms.” In Java, it allows objects to take multiple forms. There are two types:

    1. Compile-time polymorphism (Method Overloading)
    2. Runtime polymorphism (Method Overriding)

    Example 1: Method Overloading

    class Calculator {
        int add(int a, int b) {
            return a + b;
        }
    
        double add(double a, double b) {
            return a + b;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            System.out.println(calc.add(5, 10));      // Calls int method
            System.out.println(calc.add(5.5, 10.5));  // Calls double method
        }
    }
    

    Example 2: Method Overriding

    class Animal {
        void sound() {
            System.out.println("Animal makes sound");
        }
    }
    
    class Cat extends Animal {
        @Override
        void sound() {
            System.out.println("Cat meows");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Animal myCat = new Cat();
            myCat.sound(); // Runtime polymorphism
        }
    }
    

    Explanation:

    • Overloading – Same method name, different parameters.
    • Overriding – Child class provides its own implementation of a parent method.

    πŸ‘‰ To explore Polymorphism in detail with examples, check out this guide on Polymorphism in Java.

    5. Abstraction

    Abstraction is the concept of hiding implementation details and showing only functionality.

    • Achieved using abstract classes or interfaces.

    Example 1: Abstract Class

    abstract class Shape {
        abstract void area(); // abstract method
    }
    
    class Circle extends Shape {
        double radius;
    
        Circle(double radius) {
            this.radius = radius;
        }
    
        void area() {
            System.out.println("Circle area: " + (3.14 * radius * radius));
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Shape shape = new Circle(5);
            shape.area();
        }
    }
    

    Example 2: Interface

    interface Vehicle {
        void run();
    }
    
    class Bike implements Vehicle {
        public void run() {
            System.out.println("Bike is running");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Vehicle bike = new Bike();
            bike.run();
        }
    }
    

    Explanation:

    • Abstract classes and interfaces allow you to define β€œwhat” a class should do, without specifying β€œhow.”

    To dive deeper into Abstraction with practical examples, check out this guide on Abstraction in Java .

    Other Important OOP Concepts in Java

    1. Constructors – Special methods to initialize objects.
    2. This Keyword – Refers to the current object instance.
    3. Super Keyword – Refers to the parent class object.
    4. Final Keyword – Used to declare constants, prevent inheritance or method overriding.
    5. Static Keyword – For class-level variables and methods.

    OOPs Concepts in Real World Example

    Imagine a Banking System:

    • Class: BankAccount
    • Object: account1 for John, account2 for Alice
    • Encapsulation: balance is private; access via getters/setters
    • Inheritance: SavingsAccount extends BankAccount
    • Polymorphism: Different types of accounts have different calculateInterest() methods
    • Abstraction: Interface Transaction defines deposit() and withdraw() methods

    Conclusion

    OOPs concepts in Java make programs:

    • Modular – Code is divided into classes/objects
    • Reusable – Through inheritance and polymorphism
    • Secure – Through encapsulation
    • Easy to maintain – Abstraction hides complex logic

    Understanding these principles is crucial to mastering Java and building scalable applications.

  • String Constant Pool

    βœ… 1. Introduction

    The String object is one of the most commonly used classes in the Java programming language.

    In this article, we’ll explore the String Constant Pool (or String Pool)β€” a special memory region where the JVM stores String literals to optimize memory usage and performance.

    βœ… Why String Pool Is Important
    1. ➀ Memory Efficiency
      • Prevents duplicate string objects.
      • Saves memory by reusing existing strings.
    2. ➀ Performance Optimization
      • String comparison using == works for string literals since they reference the same object.
    3. ➀ Immutable Design Enables Pooling
      • Since strings are immutable, sharing the same object across the application is safe.

    βœ… 2. String Interning

    Because Strings in Java are immutable, the JVM can save memory by storing only one copy of each string literal in a special area called the String Pool. This process is called interning.

    When we create a String variable and assign a value using a literal, the JVM first checks the String Pool to see if the same string already exists.

    • If it exists, the JVM returns a reference to the existing string without creating a new object.
    • If it doesn’t exist, the new string is added to the pool, and its reference is returned.

    Let’s see a simple example to understand how this works.

    βœ”οΈ Example:

    public class StringInterningExample {
        public static void main(String[] args) {
            String str1 = "Java Knowledge Base";
            String str2 = "Java Knowledge Base";
    
            System.out.println(str1 == str2);  // Output: true
        }
    }
    

    πŸ‘‰ Both str1 and str2 point to the same object in the String Pool.

    βœ… 3. Strings Allocated Using the Constructor

    When we create a String using the new operator, the JVM always creates a new object in the heap, separate from the String Pool.

    βœ”οΈ Example:

    public class NewStringExample {
        public static void main(String[] args) {
            String literalString = "Java Knowledge Base";
            String constructedString = new String("Java Knowledge Base");
    
            System.out.println(literalString == constructedString);  // Output: false
        }
    }
    

    πŸ‘‰ Explanation:

    • literalString points to the interned string in the pool.
    • constructedString points to a new object in heap memory.

    βœ… 4. String Literal vs String Object

    When we create a String using the new() operator, a new object is always created in heap memory. But when we use a String literal like "Java Knowledge Base", the JVM checks the String Pool first β€” if the string already exists, it reuses it; otherwise, it adds the new string to the pool for future use.

    Creation TypeLocation in MemoryReuses Existing Object?
    String LiteralString constant PoolYes
    new String()Heap MemoryNo

    βœ”οΈ Example Comparison:

    public class StringComparisonExample {
        public static void main(String[] args) {
            String first = "Java Knowledge Base";
            String second = "Java Knowledge Base";
            System.out.println(first == second);  // Output: true
    
            String third = new String("Java Knowledge Base");
            String fourth = new String("Java Knowledge Base");
            System.out.println(third == fourth);  // Output: false
    
            String fifth = "Java Knowledge Base";
            String sixth = new String("Java Knowledge Base");
            System.out.println(fifth == sixth);  // Output: false
        }
    }

    πŸ‘‰ Conclusion:
    Prefer using string literals for better performance and memory usage.

    βœ… 5. Manual Interning

    We can manually add a String to the Java String Pool by using the intern() method.

    When we intern a String, its reference is stored in the pool, and the JVM will use this reference whenever the same string is needed.

    Let’s see a simple example to understand this.

    βœ”οΈ Example:

    public class ManualInternExample {
        public static void main(String[] args) {
            String constantString = "interned Java Knowledge Base";
            String newString = new String("interned Java Knowledge Base");
    
            System.out.println(constantString == newString);  // Output: false
    
            String internedString = newString.intern();
    
            System.out.println(constantString == internedString);  // Output: true
        }
    }
    

    πŸ‘‰ Manual interning ensures the string reference points to the pool object.

    βœ… 6. Garbage Collection

    Before Java 7, the String Pool was stored in the PermGen space, which has a fixed size and cannot grow during program execution. It also could not be garbage collected.

    This meant that if we interned too many strings, we could get an OutOfMemoryError.

    From Java 7 onwards, the String Pool is stored in the Heap memory, which can be garbage collected.
    This allows unused strings to be removed, reducing the chance of running out of memor

    πŸ‘‰ Benefit:
    Reduces the risk of OutOfMemoryError when interning many strings.

    βœ… 7. Performance and Optimizations

    In Java 6, the only way to optimize the String Pool was by increasing the PermGen space using the JVM option:

    -XX:MaxPermSize=1G

    From Java 7 onwards, we have better ways to manage and view the String Pool.

    Here are two useful JVM options to see the String Pool details:

    -XX:+PrintFlagsFinal
    -XX:+PrintStringTableStatistics

    If we want to increase the pool size (number of buckets), we can use this option:

    -XX:StringTableSize=4901

    Before Java 7u40, the default pool size was 1009 buckets.
    From Java 7u40 to Java 11, the default size was 60013 buckets, and in newer versions, it increased to 65536 buckets.

    πŸ‘‰ Keep in mind:
    Increasing the pool size uses more memory but makes inserting strings into the pool faster.

    πŸ’‘ Note:

    • Until Java 8, Strings were stored as a char[] using UTF-16 encoding, where each character used 2 bytes of memory.
    • From Java 9 onwards, Java introduced Compact Strings.
      Depending on the content, it uses either byte[] or char[] internally.
    • This helps save heap memory and reduces the work of the Garbage Collector, making Java programs more efficient.

    🎯Conclusion

    • In this guide, we explored how the JVM optimizes memory for String objects using the String Pool and Interning, and why immutability is a core part of the design.
    • By leveraging string literals and the intern() method, we can improve both performance and memory efficiency in Java applications.

  • Why String Is Immutable in Java

    A String in Java is a widely used object that stores a sequence of characters.
    One of the most important properties of the String class in Java is that it is immutable.

    βœ… What Is an Immutable Object?

    An immutable object is an object whose state (data) cannot be changed after it is created.

    • Once a string is created, its content cannot be altered.
    • Any modification creates a new string object.

    βœ”οΈ Example of Immutable Behavior

    public class ImmutableStringExample {
        public static void main(String[] args) {
            String str = "Java";
            str.concat("Knowledge Base");  // This does NOT modify the original str
            
            System.out.println(str);  // Output: Java 
        }
    }

    πŸ‘‰ To get the modified string, you must explicitly assign it:

    str = str.concat("Knowledge Base");
    System.out.println(str);  // Output: Java Knowledge Base

    βœ… Why Is String Immutable in Java?

    1️⃣ String Pool Benefit

    When dealing with strings in Java, a key concept that improves performance and memory management is the String Constant Pool (or String Pool).

    βœ… What Is the String Pool?

    • The String Pool is a special memory region in the Java heap.
    • It stores unique string literals.
    • Every time a string literal is created using double quotes (" "), the JVM checks the pool first:
      • If the string already exists β†’ It reuses the same reference.
      • If the string doesn’t exist β†’ It adds the string to the pool.

    βœ… Example: String Pool Behavior (literals)

    public class StringPoolExample {
        public static void main(String[] args) {
            String str1 = "Java Knowledge Base";
            String str2 = "Java Knowledge Base";
            System.out.println(str1 == str2);  // Output: true
        }
    }

    πŸ‘‰ Explanation:

    • Both str1 and str2 point to the same object in the String Pool.
    • The == operator returns true because both variables reference the same memory address.

    βœ… Example: Using new Keyword

    public class StringPoolExample2 {
        public static void main(String[] args) {
            String str1 = "Java Knowledge Base";
            String str3 = new String("Java Knowledge Base");
    
            System.out.println(str1 == str3);  // Output: false
        }
    }

    πŸ‘‰ Explanation:

    • str1 refers to the pooled string object.
    • str3 refers to a new object in the heap memory.
    • Therefore, str1 == str3 is false.

    2️⃣ Security

    Key Use Cases Where Strings Play a Critical Role:

    • Database connection URLs
    • Usernames and passwords
    • Network connections

    If strings were mutable, any malicious code running in the same application or process could modify the string content unexpectedly, leading to potential security breaches.

    βœ… Example: Password in String (Immutable)
    public class SecurityExample {
        public static void main(String[] args) {
            String password = "SuperSecret123";  // Stored in String Constant Pool
    
            // Imagine some malicious code tries to change the password
            // Since String is immutable, the original value remains unchanged
            password.concat("MaliciousPart");  // Creates a new String object, does not alter original
    
            System.out.println("Password: " + password);  // Output: SuperSecret123
        }
    }
    

    πŸ‘‰ Explanation:

    • The call to password.concat("MaliciousPart") creates a new string but does NOT modify the original password.
    • This behavior protects sensitive data from being tampered with by malicious code.
    βœ… Contrast: Mutable Example (Hypothetical MutableString)
    class MutableString {
        String value;
    
        MutableString(String value) {
            this.value = value;
        }
    
        void setValue(String newValue) {
            this.value = newValue;
        }
    }
    
    public class MutableExample {
        public static void main(String[] args) {
            MutableString password = new MutableString("SuperSecret123");
    
            // Malicious code can change the value
            password.setValue("HackedPassword!");
    
            System.out.println("Password: " + password.value);  // Output: HackedPassword!
        }
    }
    

    πŸ‘‰ Why This Is Dangerous:

    • Sensitive data like passwords can be altered at runtime.
    • Could allow attackers to inject values or manipulate security-critical variables.

    3️⃣ Hashcode Synchronization

    One of the important reasons why Java’s String class is immutable is to ensure hashcode consistency.

    • The hashCode() method for a string is computed only once and cached for future use.
    • Immutability guarantees that the string’s hashcode remains constant throughout its lifetime, which is essential for reliably storing and retrieving strings in hash-based collections like HashMap.
    βœ… What Is hashCode()?
    • The hashCode() method returns an integer value representing the content of the object.
    • It is heavily used in hash-based collections like:
      • HashMap
      • HashSet
      • Hashtable

    βœ… Why Hashcode Consistency Matters

    • Collections like HashMap store key-value pairs in buckets based on the hashcode of the key.
    • If the content of a key (a string, in this case) were to change after being added to a map:
      • The hashcode would change.
      • The object would be stored in the wrong bucket.
      • Retrieving the key later would fail.

    Example:

    public class HashcodeExample {
        public static void main(String[] args) {
            String str = "Java";
            
            // First computation of hashcode
            int hash1 = str.hashCode();
            
            // Second computation of hashcode
            int hash2 = str.hashCode();
            
            System.out.println("Hash1: " + hash1);
            System.out.println("Hash2: " + hash2);
            System.out.println("Hashes are equal: " + (hash1 == hash2));  // Output: true
        }
    }
    

    πŸ‘‰ Why This Works:

    • Subsequent calls return the same value without recomputation.
    • The string content "Java" cannot change because of immutability.
    • The JVM computes the hashcode only once and caches it.

    4️⃣ Thread Safety

    Immutable strings are inherently thread-safe:

    • Multiple threads can share the same String object safely without the need for explicit synchronization.
    • This prevents race conditions or unexpected behavior in a multithreaded environment.
    • No risk of data inconsistency or corruption.

    5️⃣ Performance Optimization

    βœ”οΈ How Does Immutability Improve Performance?
    • Because strings are immutable, they can be safely reused from the string pool.
    • Avoids unnecessary object creation when using literals.

    βœ… Summary Table: Why Strings Are Immutable

    ReasonBenefit
    String PoolMemory optimization & reuse
    SecurityImmutable sensitive data
    Hashcode ConsistencyReliable for HashMap & HashSet
    Thread SafetySafe concurrent access
    PerformanceAvoid repeated object creation
  • String Initialization in Java

    In Java, String is a widely used class that represents a sequence of characters. Strings are immutable objects, meaning once created, their values cannot be changed.

    There are two common ways to initialize a String in Java:

    1. Using String Literal
    2. Using new Keyword

    1️⃣. String Initialization using String Literal

    When you initialize a string using a literal, Java checks the πŸ‘‰String Constant Pool first.
    If the string already exists in the pool, Java reuses it. Otherwise, a new string is created in the pool.

    βœ”οΈ Syntax:

    String str1 = "Java Knowledge Base";

    βœ”οΈ Example:

    public class StringInitializationExample {
        public static void main(String[] args) {
            String str1 = "Java Knowledge Base";
            String str2 = "Java Knowledge Base";
            
            System.out.println(str1 == str2);  // Output: true
        }
    }
    

    πŸ‘‰ Explanation:

    • Both str1 and str2 point to the same object in the String Pool.
    • The == operator returns true because they reference the same memory location.

    2️⃣. String Initialization using new Keyword

    When using the new keyword, Java creates a new String object in the heap memory, even if an identical string exists in the pool.

    βœ”οΈ Syntax:

    String str3 = new String("Java Knowledge Base");

    βœ”οΈ Example:

    public class StringInitializationExample {
        public static void main(String[] args) {
            String str1 = "Java Knowledge Base";
            String str3 = new String("Java Knowledge Base");
            
            System.out.println(str1 == str3);  // Output: false
        }
    }

    πŸ‘‰ Explanation:

    • str1 points to the String Pool object.
    • str3 points to a different object in the heap.
    • The == operator returns false because they reference different memory locations.

    ⚑ Key Differences Between Literal and new Keyword Initialization

    PropertyString Literalnew Keyword
    Memory LocationString PoolHeap
    Memory ReuseYesNo
    PerformanceFasterSlightly slower due to object creation
    Comparison (==) Resulttrue (if same content)false (different objects)

    βœ… Image Diagram

    Here is a simple diagram illustrating the two types of initialization:


    3️⃣. String Declaration

    When you declare a string variable without assigning any value, it just reserves a reference in memory but doesn’t point to any object yet.

    βœ”οΈ Syntax:

    String str;

    βœ”οΈ Example:

    public class StringDeclarationExample {
        public static void main(String[] args) {
            String str;  // Only declared, not initialized
    
            // System.out.println(str); // ❌ Compilation Error: variable str might not have been initialized
        }
    }
    

    πŸ‘‰ Important Points:

    • For local variables, Java does NOT assign a default value, so accessing str without initializing it causes a compilation error.
    • For instance (class member) variables, Java automatically assigns null by default.

    βœ… Example with Class Member:

    public class Example {
        String str;  // Class member variable
    
        public void printString() {
            System.out.println(str);  // Output: null
        }
    
        public static void main(String[] args) {
            Example example = new Example();
            example.printString();
        }
    }
    

    πŸ‘‰ Output:

    null

    4️⃣. Empty String

    An empty string is a string that contains no characters but is a valid String object in memory.
    It is explicitly initialized as "" (double quotes with no characters inside).

    βœ”οΈ Syntax:

    String emptyStr = "";
    

    βœ”οΈ Example:

    public class EmptyStringExample {
        public static void main(String[] args) {
            String emptyStr = "";  // Initialized as an empty string
    
            System.out.println("Length of emptyStr: " + emptyStr.length());  // Output: 0
            System.out.println("Empty String: '" + emptyStr + "'");          // Output: ''
        }
    }
    

    πŸ‘‰ Key Points:

    • emptyStr is a valid object.
    • Its length is 0.
    • You can safely call methods on it, like length(), isEmpty(), etc.

    5️⃣. Null Value

    A null string is a reference that does NOT point to any object in memory.
    It indicates the absence of any string object.

    βœ”οΈ Syntax:

    String nullStr = null;

    βœ”οΈ Example:

    public class NullStringExample {
        public static void main(String[] args) {
            String nullStr = null;
    
            System.out.println(nullStr);  // Output: null
    
            // The following line throws NullPointerException
            // System.out.println(nullStr.length());
        }
    }
    

    πŸ‘‰ Important Notes:

    • You can safely print a null string reference: it prints null.
    • But calling methods on it (like .length(), .isEmpty()) will throw a NullPointerException.

    βœ… Comparison Table: Empty String vs Null Value

    PropertyEmpty String ("")Null Value (null)
    Memory AllocationYes (an object in memory)No object, just a reference
    String Length0Accessing length causes NullPointerException
    Usage ExampleString s = "";String s = null;
    Method CallsSafe (e.g., s.length())Unsafe (throws NullPointerException)
    PurposeRepresents no charactersRepresents absence of object

    βœ… Best Practices

    • Use empty strings ("") when you want to represent an empty text field or no characters.
    • Use null values when the reference should be uninitialized or explicitly indicate “no object”.
    • Always perform null checks before invoking methods on a String variable to avoid exceptions.

    βœ…πŸš¨ Key Points:

    ScenarioExample Code
    Declaration onlyString str; (Must be initialized before use)
    Empty StringString emptyStr = "";
    Null ValueString nullStr = null;

    Understanding the difference helps in writing robust and error-free code.


    🎯Conclusion

    • Prefer using String literals when possible to save memory and improve performance.
    • Use the new keyword when a distinct String object is required.