Category: Java

  • Methods

    Java Instance Methods

    Instance methods are methods that are called on an instance (object) of the class they belong to. To invoke an instance method, you first need to create an object of the class.

    Example syntax:

    public void display(String message) {
        // Code to be executed
    }

    Instance methods can return any data type, including primitive types (e.g., intfloat) or user-defined types.

    Memory Allocation of Instance Methods

    Instance methods themselves are stored in the Permanent Generation (PermGen) space of the heap until Java 7, but since Java 8, they are stored in Metaspace. However, their local variables and parameters (arguments passed) are stored in the stack.

    These methods can be called from within the class in which they are defined or from other classes, depending on the access modifiers.

    Key Points:

    1. Instance methods belong to objects, not to the class itself.
    2. They are stored in a single memory location and can access the object they belong to using the this reference.
    3. Instance methods can be overridden, as they are resolved at runtime through dynamic binding.

    Example:

    // Example to demonstrate accessing an instance method.
    class Car {
    
        String model = "";
    
        // Instance method to set car model
        public void setModel(String model) {
            this.model = model;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
    
            // Create an instance of the Car class
            Car myCar = new Car();
    
            // Call the instance method to set model
            myCar.setModel("Tesla Model S");
            System.out.println(myCar.model);
        }
    }

    Output:

    Tesla Model S
    Java Static Methods

    Static methods, unlike instance methods, belong to the class rather than an object. They can be called without creating an instance of the class.

    Example syntax:

    public static void display(String message) {
        // Code to be executed
    }

    Static methods must include the static modifier in their declaration.

    Memory Allocation of Static Methods

    Static methods are stored in the Metaspace area (starting from Java 8) since they are associated with the class rather than any object. However, their local variables and arguments are stored in the stack.

    Key Points:

    1. Static methods are called using the class name, without the need for an instance.
    2. They are shared across all instances of the class.
    3. Static methods cannot be overridden, but they can be hidden by subclass methods if both superclass and subclass declare a static method with the same name. This is called Method Hiding.

    Example:

    // Example to demonstrate accessing static methods.
    class MathUtil {
    
        public static double pi = 3.14159;
    
        // Static method to calculate the circumference of a circle
        public static double circumference(double radius) {
            return 2 * pi * radius;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
    
            // Access static method and field using class name
            double result = MathUtil.circumference(5);
            System.out.println("Circumference: " + result);
    
            // Access static method using an instance (though it's unnecessary)
            MathUtil util = new MathUtil();
            double result2 = util.circumference(7);
            System.out.println("Circumference: " + result2);
        }
    }

    Output:

    Circumference: 31.4159
    Circumference: 43.98226

    Abstract Method

    In Java, there are situations where we need only method declarations in superclass, which is accomplished using the abstract type modifier. Abstraction can be implemented through abstract classes and methods. In this discussion, we will explore Java Abstract Methods.

    Java Abstract Method

    An abstract method serves as a blueprint for other classes or interfaces. In this context, methods are declared but not implemented. Abstract methods must be implemented by subclasses or classes that implement the respective interfaces.

    These methods are often referred to as “subclass responsibilities” because they lack implementations in the superclass. Consequently, any subclass must override these methods to provide concrete definitions.

    Declaring Abstract Methods in Java

    To declare an abstract method, use the following general syntax:

    abstract returnType methodName(parameterList);

    Note that no method body is included. Any concrete class (a class not marked with the abstract keyword) that extends an abstract class must implement all abstract methods from that class.

    Key Points about Abstract Methods

    1. Any class containing one or more abstract methods must itself be declared as abstract.
    2. A class containing an abstract method must be abstract, but the reverse is not necessarily true.
    3. If a non-abstract class extends an abstract class, it must implement all abstract methods of the abstract class; otherwise, the non-abstract class must also be declared as abstract.
    4. The following combinations of modifiers with abstract methods are illegal:final

    • abstract native
    • abstract synchronized
    • abstract static
    • abstract private
    • abstract strictf

    Example of Java Abstract Method

    Example 1: Performing Addition and Subtraction with Abstraction

    Here is a program demonstrating how to perform addition and subtraction using abstraction.

    // Java Program to implement addition and subtraction using abstraction
    
    // Abstract Class
    abstract class MathOperation {
        abstract void displayInfo();
    }
    
    // Class Add
    class Add extends MathOperation {
        void displayInfo() {
            int a = 5;
            int b = 10;
            System.out.println("Addition: " + (a + b));
        }
    }
    
    // Class Subtract
    class Subtract extends MathOperation {
        void displayInfo() {
            int c = 15;
            int d = 4;
            System.out.println("Subtraction: " + (c - d));
        }
    }
    
    // Driver Class
    public class AbstractionDemo {
        public static void main(String args[]) {
            MathOperation addition = new Add();
            addition.displayInfo();
            MathOperation subtraction = new Subtract();
            subtraction.displayInfo();
        }
    }

    Output:

    Addition: 15
    Subtraction: 11

    Example 2: Using Abstract Keyword with Classes and Methods

    Here is another example illustrating the use of the abstract keyword.

    // A Java program demonstrating the use of abstract keyword
    
    // Abstract class
    abstract class Shape {
        abstract void draw();
    
        void show() {
            System.out.println("This is a concrete method in the abstract class.");
        }
    }
    
    // Concrete class Circle
    class Circle extends Shape {
        void draw() {
            System.out.println("Drawing a circle.");
        }
    }
    
    // Driver class
    public class AbstractExample {
        public static void main(String args[]) {
            Circle circle = new Circle();
            circle.draw();
            circle.show();
        }
    }

    Output:

    Drawing a circle.
    This is a concrete method in the abstract class.

    Example 3: Abstract Class with Multiple Abstract Methods

    This program illustrates an abstract class containing multiple abstract methods.

    // Java Program to implement an abstract class with multiple abstract methods
    
    abstract class AreaCalculator {
        abstract void calculateRectangleArea(int height, int width);
        abstract void calculateSquareArea(int side);
        abstract void calculateCircleArea(float radius);
    }
    
    // Class implementing the abstract methods
    class Calculator extends AreaCalculator {
        public void calculateRectangleArea(int height, int width) {
            int area = height * width;
            System.out.println("Area of rectangle: " + area);
        }
    
        public void calculateSquareArea(int side) {
            int area = side * side;
            System.out.println("Area of square: " + area);
        }
    
        public void calculateCircleArea(float radius) {
            float area = 3.14f * radius * radius;
            System.out.println("Area of circle: " + area);
        }
    
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            calc.calculateRectangleArea(10, 5);
            calc.calculateSquareArea(4);
            calc.calculateCircleArea(3.0f);
        }
    }

    Output:

    Area of rectangle: 50
    Area of square: 16
    Area of circle: 28.259999
    Abstract Method in Interface 

    All methods in an interface are inherently public and abstract, allowing for the declaration of abstract methods within an interface.

    Here’s an implementation of this concept:

    // Java Program to demonstrate abstract methods in an interface
    
    // Declaring an interface
    interface Operations {
        int addTwoNumbers(int a, int b);
        int addThreeNumbers(int a, int b, int c);
    }
    
    // Main Class
    public class InterfaceExample implements Operations {
        public int addTwoNumbers(int a, int b) {
            return a + b;
        }
    
        public int addThreeNumbers(int a, int b, int c) {
            return a + b + c;
        }
    
        public static void main(String args[]) {
            Operations ops = new InterfaceExample();
            System.out.println("Sum of two numbers: " + ops.addTwoNumbers(5, 10));
            System.out.println("Sum of three numbers: " + ops.addThreeNumbers(5, 10, 15));
        }
    }

    Output:

    Sum of two numbers: 15
    Sum of three numbers: 30
    Final Method in Abstract Class

    While you cannot use final with an abstract method, you can define a final method in an abstract class.

    class Vehicle {
        final void fuel() {
            System.out.println("Fuel type: Petrol");
        }
    }
    
    class Car extends Vehicle {
        // This will cause an error
        // void fuel() { System.out.println("Fuel type: Diesel"); }
    }

    Output:

    Error: Cannot override the final method from Vehicle
    Static Methods Cannot Be Overridden (Method Hiding):

    When a static method is redefined in a subclass, it is method hiding, not overriding.

    class Parent {
        static void show() {
            System.out.println("Parent static show()");
        }
    }
    
    class Child extends Parent {
        static void show() {
            System.out.println("Child static show()");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Parent p = new Child();
            p.show(); // Calls Parent's static show()
        }
    }

    Output:

    Parent static show()
    Private Methods Cannot Be Overridden:

    Private methods are not visible to the child class and hence cannot be overridden.

    class Parent {
        private void message() {
            System.out.println("Parent's private message()");
        }
    
        public void callMessage() {
            message();
        }
    }
    
    class Child extends Parent {
        private void message() {
            System.out.println("Child's private message()");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Parent p = new Child();
            p.callMessage(); // Calls Parent's message()
        }
    }

    Output:

    Parent's private message()
    Covariant Return Type:

    The return type of an overriding method can be a subtype of the original return type.

    class Parent {
        public Object getObject() {
            return "Parent object";
        }
    }
    
    class Child extends Parent {
        @Override
        public String getObject() {
            return "Child object";
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Parent p = new Child();
            System.out.println(p.getObject());
        }
    }

    Output:

    Child object
    the Parent Class Method using super:

    The overridden method in the subclass can call the method from the parent class using the super keyword.

    class Parent {
        void display() {
            System.out.println("Parent display()");
        }
    }
    
    class Child extends Parent {
        @Override
        void display() {
            super.display();
            System.out.println("Child display()");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Child c = new Child();
            c.display(); // Calls both Parent's and Child's display()
        }
    }

    Output:

    Parent display()
    Child display()
    Method Overriding vs Method Overloading

    1. Overloading is when methods have the same name but different signatures.
    Overriding is when a subclass provides a specific implementation of a method that already exists in the parent class.
    2. Overloading is a form of compile-time polymorphism (method resolution at compile time), while overriding is a form of run-time polymorphism (method resolution at runtime).

    Overriding

    Overriding is a feature in Java where a subclass or child class provides a specific implementation of a method that is already present in its parent class or superclass. When a method in a subclass has the same name, parameters (signature), and return type (or a subtype) as a method in its superclass, it overrides the method from the superclass.

    Method Overriding enables Run-Time Polymorphism. This means that the method that gets executed depends on the object that is used to call it, not the reference type. If the object is of the subclass, the subclass method will be called; otherwise, the superclass method will be executed.

    Example of Method Overriding:

    // Superclass
    class Animal {
        void sound() { System.out.println("Animal makes a sound"); }
    }
    
    // Subclass
    class Dog extends Animal {
        @Override
        void sound() {
            System.out.println("Dog barks");
        }
    }
    
    // Driver class
    class Main {
        public static void main(String[] args) {
            Animal a1 = new Animal();
            a1.sound();  // Calls Animal's sound()
    
            Animal a2 = new Dog();
            a2.sound();  // Calls Dog's sound (Run-time Polymorphism)
        }
    }

    Output:

    Animal makes a sound
    Dog barks
    Rules for Method Overriding

    1. Access Modifiers in Overriding : The overridden method in the subclass can provide more visibility, but not less. For example, a protected method in the superclass can be overridden as public in the subclass, but not private.

    class Vehicle {
        protected void start() {
            System.out.println("Vehicle starts");
        }
    }
    
    class Car extends Vehicle {
        @Override
        public void start() {  // More accessible
            System.out.println("Car starts");
        }
    }
    
    class Main {
        public static void main(String[] args) {
            Vehicle v = new Car();
            v.start();  // Calls Car's start()
        }
    }

    Output:

    Car starts

    2. Final Methods Cannot Be Overridden : If a method is declared as final in the superclass, it cannot be overridden in the subclass.

    class Bird {
        final void fly() {
            System.out.println("Bird is flying");
        }
    }
    
    class Eagle extends Bird {
        // This would produce an error if uncommented
        // void fly() { System.out.println("Eagle flies faster"); }
    }

    Output:

    Compilation error: cannot override final method

    3. Static Methods and Method Hiding : Static methods cannot be overridden; they are hidden. A subclass can define a static method with the same signature as the one in its superclass, but this will hide the method in the superclass rather than overriding it.

    class Parent {
        static void display() {
            System.out.println("Parent display");
        }
    
        void show() {
            System.out.println("Parent show");
        }
    }
    
    class Child extends Parent {
        static void display() {
            System.out.println("Child display");
        }
    
        @Override
        void show() {
            System.out.println("Child show");
        }
    }
    
    class Main {
        public static void main(String[] args) {
            Parent p = new Child();
            p.display();  // Calls Parent's static method
            p.show();     // Calls Child's overridden method
        }
    }

    Output:

    Parent display
    Child show

    4. Private Methods Cannot Be Overridden : Private methods in the superclass cannot be overridden by subclasses. They are not visible to subclasses and are resolved at compile time.

    class Super {
        private void secretMethod() {
            System.out.println("Super's secret method");
        }
    
        public void callSecret() {
            secretMethod();
        }
    }
    
    class Sub extends Super {
        private void secretMethod() {
            System.out.println("Sub's secret method");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Super sup = new Super();
            sup.callSecret();  // Calls Super's private method
        }
    }

    Output:

    Super's secret method

    5. Covariant Return Type in Overriding : The return type of the overriding method can be a subclass of the return type of the overridden method.

    public class BitwiseOperators {
        public static void main(String[] args) {
            int a = 5; // 0101 in binary
            int b = 3; // 0011 in binary
            System.out.println("a & b: " + (a & b)); // AND operation
            System.out.println("a | b: " + (a | b)); // OR operation
        }
    }

    Output:

    class SuperClass {
        public Object getObject() {
            return new Object();
        }
    }
    
    class SubClass extends SuperClass {
        @Override
        public String getObject() {
            return "This is a String";
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            SuperClass obj = new SubClass();
            System.out.println(obj.getObject());
        }
    }

    Output:

    This is a String

    6. Calling the Superclass Method : We can call the superclass version of the overridden method using the super keyword.

    class Superhero {
        void power() {
            System.out.println("Superhero has generic powers");
        }
    }
    
    class Superman extends Superhero {
        @Override
        void power() {
            super.power();
            System.out.println("Superman can fly and has super strength");
        }
    }
    
    class Main {
        public static void main(String[] args) {
            Superman clark = new Superman();
            clark.power();
        }
    }

    Output:

    Superhero has generic powers
    Superman can fly and has super strength
    Overriding and Exception Handling

    1. Unchecked Exceptions: If the superclass method does not throw any exceptions, the subclass can only throw unchecked exceptions when overriding it.

    class SuperClass {
        void riskyMethod() {
            System.out.println("SuperClass riskyMethod");
        }
    }
    
    class SubClass extends SuperClass {
        @Override
        void riskyMethod() throws ArithmeticException {
            System.out.println("SubClass riskyMethod");
        }
    }

    2. Checked Exceptions : If the superclass method throws an exception, the overriding method can throw the same exception or its subclass, but not a higher-level exception.

    class Animal {
        void eat() throws Exception {
            System.out.println("Animal is eating");
        }
    }
    
    class Dog extends Animal {
        @Override
        void eat() throws RuntimeException {
            System.out.println("Dog is eating");
        }
    }

    Method Overloading

    In Java, Method Overloading allows multiple methods to share the same name but differ in parameters—either by the number of parameters, types of parameters, or a combination of both. This feature is also called Compile-time PolymorphismStatic Polymorphism, or Early Binding. When overloaded methods are present, Java gives priority to the most specific match among the parameters.

    Example of Method Overloading

    // Java program demonstrating method overloading
    
    public class Calculator {
        // Overloaded add() method that accepts two integer parameters
        public int add(int a, int b) {
            return a + b;
        }
    
        // Overloaded add() method that accepts three integer parameters
        public int add(int a, int b, int c) {
            return a + b + c;
        }
    
        // Overloaded add() method that accepts two double parameters
        public double add(double a, double b) {
            return a + b;
        }
    
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            System.out.println(calc.add(5, 10));         // Two integers
            System.out.println(calc.add(5, 10, 15));     // Three integers
            System.out.println(calc.add(4.5, 3.5));      // Two doubles
        }
    }

    Output:

    15
    30
    8.0
    Ways to Achieve Method Overloading in Java:

    1. Changing the Number of Parameters
    2. Changing the Data Types of the Arguments
    3. Changing the Order of Parameters

    1. Changing the Number of Parameters : This approach achieves method overloading by varying the number of input parameters in methods that share the same name.

    // Java program to demonstrate method overloading by changing the number of parameters
    
    class Multiply {
        // Multiply two integers
        public int multiply(int a, int b) {
            return a * b;
        }
    
        // Multiply three integers
        public int multiply(int a, int b, int c) {
            return a * b * c;
        }
    
        public static void main(String[] args) {
            Multiply obj = new Multiply();
            System.out.println("Product of two integers: " + obj.multiply(2, 3));
            System.out.println("Product of three integers: " + obj.multiply(2, 3, 4));
        }
    }

    Output:

    Product of two integers: 6
    Product of three integers: 24

    2. Changing Data Types of the Arguments : This method achieves overloading by having the same method name but with different parameter types.

    // Java program to demonstrate method overloading by changing the data types of parameters
    
    class Volume {
        // Calculate volume using integer values
        public int calculateVolume(int a, int b, int c) {
            return a * b * c;
        }
    
        // Calculate volume using double values
        public double calculateVolume(double a, double b, double c) {
            return a * b * c;
        }
    
        public static void main(String[] args) {
            Volume vol = new Volume();
            System.out.println("Volume with integers: " + vol.calculateVolume(2, 3, 4));
            System.out.println("Volume with doubles: " + vol.calculateVolume(2.5, 3.5, 4.5));
        }
    }

    Output:

    Volume with integers: 24
    Volume with doubles: 39.375

    3. Changing the Order of Parameters : You can achieve method overloading by altering the order in which parameters are passed.

    // Java program to demonstrate method overloading by changing the order of parameters
    
    class Display {
        // Display information by name followed by age
        public void showDetails(String name, int age) {
            System.out.println("Name: " + name + ", Age: " + age);
        }
    
        // Display information by age followed by name
        public void showDetails(int age, String name) {
            System.out.println("Age: " + age + ", Name: " + name);
        }
    
        public static void main(String[] args) {
            Display obj = new Display();
            obj.showDetails("Alice", 30);
            obj.showDetails(25, "Bob");
        }
    }

    Output:

    Name: Alice, Age: 30
    Age: 25, Name: Bob
    What if the exact prototype doesn’t match?

    In situations where the parameters don’t match any exact method signature, Java prioritizes methods based on data type compatibility. The compiler tries to:

    1. Convert the parameter to a higher data type within the same group (e.g., from byte to int).
    2. If no match is found, it attempts to move to a higher data type in another group (e.g., from int to float).

    class Demo {
        public void display(int x) {
            System.out.println("Integer: " + x);
        }
    
        public void display(String s) {
            System.out.println("String: " + s);
        }
    
        public void display(byte b) {
            System.out.println("Byte: " + b);
        }
    }
    
    public class UseDemo {
        public static void main(String[] args) {
            byte a = 20;
            Demo obj = new Demo();
    
            obj.display(a);          // Byte method
            obj.display("Hello");     // String method
            obj.display(500);         // Integer method
            obj.display('C');         // Char is promoted to int (ASCII value)
    
            // Uncommenting the line below would cause a compilation error:
            // obj.display(10.5);     // No suitable method for double
        }
    }

    Output:

    Byte: 20
    String: Hello
    Integer: 500
    Integer: 67
    Advantages of Method Overloading
    • Enhanced Readability and Reusability: By using method overloading, the code becomes more intuitive, reducing the need for verbose method names.
    • Reduced Complexity: Methods performing similar tasks can share a name, making code easier to manage.
    • Efficiency: Overloading allows different versions of methods to handle related tasks with varying parameters, optimizing the function call.
    • Multiple Constructor Options: Overloaded constructors enable different ways to initialize objects, depending on the given arguments.
  • Polymorphism

    Difference between Inheritance and Polymorphism

    Inheritance:

    Inheritance is a feature in object-oriented programming where a new class (called derived or child class) is created by inheriting properties and behaviors (methods and variables) from an existing class (called base or parent class). It promotes code reusability and reduces redundancy.

    Types of Inheritance:

    1. Single Inheritance
    2. Multi-level Inheritance
    3. Multiple Inheritance
    4. Hybrid Inheritance
    5. Hierarchical Inheritance

    Example of Inheritance:

    #include <iostream>
    using namespace std;
    
    class A {
        int a, b;
    
    public:
        void add(int x, int y)
        {
            a = x;
            b = y;
            cout << "Addition of a + b is: " << (a + b) << endl;
        }
    };
    
    class B : public A {
    public:
        void print(int x, int y)
        {
            add(x, y);
        }
    };
    
    int main()
    {
        B b1;
        b1.print(5, 6);
        return 0;
    }

    Output:

    Addition of a + b is: 11

    Here, class B inherits the add() method from class A.

    Polymorphism:

    Polymorphism is a feature in object-oriented programming where an object can take multiple forms. Polymorphism allows one task to be performed in different ways, either at compile-time or run-time.

    Types of Polymorphism:
    1. Compile-time polymorphism (Method Overloading)
    2. Run-time polymorphism (Method Overriding)

    Example of Polymorphism:

    #include <iostream>
    using namespace std;
    
    class A {
        int a, b, c;
    
    public:
        // Compile-time polymorphism (Method Overloading)
        void add(int x, int y)
        {
            a = x;
            b = y;
            cout << "Addition of a + b is: " << (a + b) << endl;
        }
    
        void add(int x, int y, int z)
        {
            a = x;
            b = y;
            c = z;
            cout << "Addition of a + b + c is: " << (a + b + c) << endl;
        }
    
        // Run-time polymorphism (Method Overriding)
        virtual void print()
        {
            cout << "Class A's method is running" << endl;
        }
    };
    
    class B : public A {
    public:
        void print()
        {
            cout << "Class B's method is running" << endl;
        }
    };
    
    int main()
    {
        A a1;
    
        // Compile-time polymorphism (Method Overloading)
        a1.add(6, 5);
        a1.add(1, 2, 3);
    
        B b1;
    
        // Run-time polymorphism (Method Overriding)
        b1.print();
    }

    Output:

    Addition of a + b is: 11
    Addition of a + b + c is: 6
    Class B's method is running
    Difference between Inheritance and Polymorphism:
     InheritancePolymorphism
     Inheritance allows a new class (derived class) to inherit features from an existing class (base class).Polymorphism allows methods to take multiple forms.
     Inheritance applies to classes.Polymorphism applies to methods or functions.
     Inheritance supports code reusability and reduces code duplication.Polymorphism allows the program to choose which function to execute at compile-time (overloading) or run-time (overriding).
     Inheritance can be single, multiple, hierarchical, multilevel, or hybrid.Polymorphism can be compile-time (overloading) or run-time (overriding).
     Inheritance is used to model relationships between classes.Polymorphism allows flexibility in implementing methods.

    Example of Inheritance:
    A class Car can be derived from a class Vehicle, and Car can further inherit properties like engine type, wheels, etc.

    Example of Polymorphism:
    The class Car can have a method setColor(), which changes the car’s color based on the input color value provided.

    Function Overriding in C++

    Method Overriding and Runtime Polymorphism in Java:

    Java supports runtime polymorphism through method overriding. Dynamic method dispatch is the mechanism that resolves which overridden method will be executed at runtime, not during compile-time.

    When a method is called on a superclass reference, Java determines which version of the method (from the superclass or subclass) to execute based on the actual object being referenced at the time of the call. This decision is made at runtime, depending on the type of the object (not the reference variable).

    A superclass reference variable can refer to an object of a subclass, which is known as upcasting. Java uses this concept to enable method overriding during runtime.

    If a superclass contains a method that is overridden by a subclass, the version of the method executed depends on the object type being referred to, even though the reference variable is of the superclass type. Below is an example demonstrating dynamic method dispatch:

    Java Example of Dynamic Method Dispatch:

    // A Java program to demonstrate Dynamic Method Dispatch
    class Animal {
        void sound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        // overriding sound() method
        void sound() {
            System.out.println("Dog barks");
        }
    }
    
    class Cat extends Animal {
        // overriding sound() method
        void sound() {
            System.out.println("Cat meows");
        }
    }
    
    public class DispatchDemo {
        public static void main(String[] args) {
            // creating objects of Animal, Dog, and Cat
            Animal animal = new Animal();
            Dog dog = new Dog();
            Cat cat = new Cat();
    
            // reference of type Animal
            Animal ref;
    
            // ref refers to Animal object
            ref = animal;
            ref.sound();  // calls Animal's version of sound()
    
            // ref refers to Dog object
            ref = dog;
            ref.sound();  // calls Dog's version of sound()
    
            // ref refers to Cat object
            ref = cat;
            ref.sound();  // calls Cat's version of sound()
        }
    }

    Output:

    Animal makes a sound
    Dog barks
    Cat meows

    Explanation:

    The above program has one superclass Animal and two subclasses Dog and Cat. Each subclass overrides the sound() method from the superclass.

    • First, an object of each class (AnimalDogCat) is created.
    • Then a reference of type Animal is used to refer to objects of different types (upcasting).
    • The version of the sound() method called depends on the actual object type at runtime.

    Example: Runtime Polymorphism with Data Members (Java):

    In Java, runtime polymorphism works with methods but not with data members (variables). Variables are not overridden, so the reference variable will always access the data member of the superclass, not the subclass.

    Java Example for Data Members:

    // Java program to show that runtime polymorphism
    // doesn't apply to data members (only methods)
    
    class Animal {
        int age = 5;
    }
    
    class Dog extends Animal {
        int age = 10;
    }
    
    public class TestDemo {
        public static void main(String[] args) {
            Animal animal = new Dog();  // object of type Dog
    
            // Data member of class Animal will be accessed
            System.out.println(animal.age);
        }
    }

    Output:

    5

    Explanation:

    In this program, both the Animal and Dog classes have a common data member age. Even though the object is of type Dog and the reference variable is of type Animal, the data member of Animal will be accessed because data members are not overridden. Therefore, animal.age refers to the superclass Animal‘s age value.

    Advantages of Dynamic Method Dispatch:

    1. Supports Method Overriding:
    Dynamic method dispatch allows Java to support method overriding, which is crucial for implementing runtime polymorphism.
    2. Provides Flexibility in Method Implementation:
    A class can define common methods that are shared by all its subclasses, while allowing each subclass to provide specific implementations for those methods.
    3. Enhances Extensibility:
    It enables subclasses to add their unique behaviors while still using the reference variable of the superclass, promoting flexibility and scalability in code.

    Difference between Compile-time and Run-time Polymorphism in Java

    Polymorphism Explained:

    The term polymorphism refers to the concept of having multiple forms. In simpler terms, polymorphism allows the same message or method to be processed in more than one way. In this discussion, we will explore the distinction between the two types of polymorphism: compile-time and runtime polymorphism.

    Compile-Time Polymorphism:

    When the binding of a method call to the method definition occurs at compile-time, it is known as compile-time polymorphism. Java resolves which method to call by examining the method signatures during compilation, which is why this type of polymorphism is also called static or early binding. Compile-time polymorphism is achieved through method overloading.

    Method Overloading refers to having multiple methods in the same class with the same name but different parameter lists (method signatures). This is one way to implement polymorphism, although the specific method varies based on the language. In Java, method overloading is resolved at compile-time.

    Here is an example demonstrating compile-time polymorphism:

    Java Example of Compile-Time Polymorphism:

    // Java program demonstrating compile-time polymorphism
    public class Example {
    
        // First version of the add method
        public static int add(int a, int b) {
            return a + b;
        }
    
        // Second version of the add method
        public static double add(double a, double b) {
            return a + b;
        }
    
        public static void main(String[] args) {
            // The first add method is called
            System.out.println(add(3, 4)); // Output: 7
    
            // The second add method is called
            System.out.println(add(3.5, 4.5)); // Output: 8.0
        }
    }

    Output:

    7
    8.0
    Run-Time Polymorphism:

    When the method binding happens at runtime, it is called runtime polymorphism. This is achieved through method overriding in Java. The Java Virtual Machine (JVM) determines which overridden method to call at runtime based on the actual object, not during compilation.

    Method Overriding occurs when a subclass provides its specific implementation of a method that is already defined in its superclass. This allows different classes to define the same method in their own way, which is resolved dynamically at runtime.

    Java Example of Run-Time Polymorphism:

    // Java program demonstrating run-time polymorphism
    
    // Parent class
    class Animal {
        public void makeSound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    // Child class
    class Dog extends Animal {
        // Overriding the parent method
        public void makeSound() {
            System.out.println("Dog barks");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Animal animalRef = new Dog();  // Upcasting
    
            // The overridden method in Dog will be called
            animalRef.makeSound();  // Output: Dog barks
        }
    }

    Output:

    Dog barks

    In this example, even though the reference variable animalRef is of type Animal, it refers to an object of type Dog. At runtime, the JVM determines which makeSound method to execute based on the object type, resulting in the Dog class’s method being called.

    Differences between Compile-Time and Run-Time Polymorphism:
    Compile-Time PolymorphismRun-Time Polymorphism
    The method call is resolved by the compiler.The method call is resolved during runtime by the JVM.
    Also known as Static bindingEarly binding, or Overloading.Also known as Dynamic bindingLate binding, or Overriding.
    Achieved by method overloading.Achieved by method overriding.
    Faster execution, as the method to execute is determined at compile time.Slower execution in comparison, as the method is determined at runtime.
    Does not involve inheritance.Requires inheritance for method overriding.
    Less flexible, since method calls are fixed at compile-time.More flexible, since method calls are resolved at runtime.
    Advantages of Polymorphism:
    • Compile-Time Polymorphism: Provides faster execution as the decision of which method to invoke is made at compile time, making the program more efficient.
    • Run-Time Polymorphism: Offers greater flexibility because method calls are determined at runtime, allowing different behaviors based on the actual object type being referenced.
  • Encapsulation

    Encapsulation in C++

    Abstraction and Encapsulation in Object-Oriented Programming (OOP)

    Abstraction and Encapsulation are key principles in Object-Oriented Programming (OOP). They are fundamental to building maintainable, reusable, and secure applications. Both concepts contribute to features like reusability, security, data hiding, and implementation concealment. Despite their relation, they serve different purposes and are implemented in unique ways. Let’s explore these differences with code examples.

    Encapsulation in Java

    Encapsulation is the process of bundling data (variables) and methods that operate on the data within a single unit or class. It ensures that the data is not accessible directly outside the class, thereby providing security and control over its modification. By restricting access to data through private variables and allowing controlled access via public methods, encapsulation prevents unauthorized access.

    In simple terms, encapsulation can be thought of as a protective shield that keeps data safe from unauthorized access. This is why it is also referred to as data hiding.

    Java Example of Encapsulation:

    // Java program demonstrating encapsulation
    
    class Student {
    
        // Private variables, accessible only through public methods
        private String studentName;
        private int studentID;
        private int studentAge;
    
        // Getter method for age
        public int getAge() {
            return studentAge;
        }
    
        // Getter method for name
        public String getName() {
            return studentName;
        }
    
        // Getter method for ID
        public int getID() {
            return studentID;
        }
    
        // Setter method for age
        public void setAge(int newAge) {
            studentAge = newAge;
        }
    
        // Setter method for name
        public void setName(String newName) {
            studentName = newName;
        }
    
        // Setter method for ID
        public void setID(int newID) {
            studentID = newID;
        }
    }
    
    public class TestEncapsulation {
        public static void main(String[] args) {
            // Creating an object of Student class
            Student obj = new Student();
    
            // Setting values of the variables
            obj.setName("John");
            obj.setAge(22);
            obj.setID(1001);
    
            // Displaying values of the variables
            System.out.println("Student's name: " + obj.getName());
            System.out.println("Student's age: " + obj.getAge());
            System.out.println("Student's ID: " + obj.getID());
        }
    }

    Output:

    Student's name: John
    Student's age: 22
    Student's ID: 1001

    In this example, encapsulation ensures that studentIDstudentName, and studentAge can only be accessed or modified through the setter and getter methods, not directly.

    Abstraction in Java

    Abstraction is the concept of showing only the relevant details to the user and hiding unnecessary implementation details. It allows you to focus on what an object does rather than how it does it. Abstraction simplifies complex systems by breaking them into smaller, more understandable parts.

    For example, you interact with a vehicle through its interface (like driving a car), but you do not need to know the internal mechanics of how the engine operates. Abstraction is often achieved in Java through abstract classes and interfaces.

    Java Example of Abstraction:

    // Java program demonstrating abstraction
    
    abstract class Appliance {
        String brand;
    
        // Abstract methods that subclasses need to implement
        abstract void turnOn();
        abstract void turnOff();
    
        // Constructor
        public Appliance(String brand) {
            this.brand = brand;
        }
    
        // Concrete method
        public String getBrand() {
            return brand;
        }
    }
    
    class WashingMachine extends Appliance {
    
        public WashingMachine(String brand) {
            super(brand);
        }
    
        @Override
        void turnOn() {
            System.out.println(brand + " Washing Machine is now ON");
        }
    
        @Override
        void turnOff() {
            System.out.println(brand + " Washing Machine is now OFF");
        }
    }
    
    class Refrigerator extends Appliance {
    
        public Refrigerator(String brand) {
            super(brand);
        }
    
        @Override
        void turnOn() {
            System.out.println(brand + " Refrigerator is now ON");
        }
    
        @Override
        void turnOff() {
            System.out.println(brand + " Refrigerator is now OFF");
        }
    }
    
    public class TestAbstraction {
        public static void main(String[] args) {
            Appliance washingMachine = new WashingMachine("LG");
            Appliance refrigerator = new Refrigerator("Samsung");
    
            washingMachine.turnOn();
            refrigerator.turnOn();
    
            washingMachine.turnOff();
            refrigerator.turnOff();
        }
    }

    Output:

    LG Washing Machine is now ON
    Samsung Refrigerator is now ON
    LG Washing Machine is now OFF
    Samsung Refrigerator is now OFF

    In this example, abstraction allows us to define the basic behavior of an appliance without showing the internal workings. The specific implementations for turnOn and turnOff are provided by the subclasses WashingMachine and Refrigerator.

    Differences Between Abstraction and Encapsulation

    AbstractionEncapsulation
    Abstraction focuses on hiding implementation details while showing only essential features to the user.Encapsulation bundles the data and methods that operate on the data within one unit and restricts direct access to some of the object’s components.
    It is used to reduce complexity and increase maintainability by defining clear interfaces.It is used to hide data and provide controlled access, enhancing security and data integrity.
    Abstraction is more about the design and involves creating abstract classes and interfaces.Encapsulation is more about the implementation, using access modifiers like privateprotected, and public.
    Problems are solved at the interface level.Problems are solved at the class level.
    Abstraction can be achieved using abstract classes and interfaces.Encapsulation is achieved using access modifiers and getter/setter methods.
    Example: A TV remote control lets you operate the TV without showing how the circuits inside work.Example: A person’s bank account details are hidden from unauthorized users, and access is granted only via public methods.
  • Abstraction keyword

    Abstraction

    In Java, the abstract keyword is a non-access modifier applied to classes and methods, but not variables. It is primarily used to achieve abstraction, one of the core principles of Object-Oriented Programming (OOP). Below are the various contexts where the abstract keyword can be utilized in Java.

    Characteristics of the abstract Keyword in Java

    The abstract keyword is used to define abstract classes and methods. Here are its key characteristics:

    • Abstract classes cannot be instantiated: An abstract class is one that cannot be instantiated directly. It serves as a base class for other classes, which are responsible for providing concrete implementations of its abstract methods.
    • Abstract methods lack a body: An abstract method is declared using the abstract keyword and has no method body. It ends with a semicolon. Any class that extends an abstract class must provide implementations for all abstract methods.
    • Abstract classes can have both abstract and concrete methods: Along with abstract methods, an abstract class can also contain concrete methods with full implementations. These methods can be used by the abstract class itself or its subclasses.
    • Abstract classes can have constructors: While abstract classes cannot be instantiated, they can define constructors. These constructors are typically called during the instantiation of a concrete subclass.
    • Abstract classes can include instance variables: Abstract classes can declare instance variables, which can be accessed by both the abstract class and its subclasses.
    • Abstract classes can implement interfaces: An abstract class can implement interfaces and provide concrete implementations of the interface methods. However, the abstract class does not need to implement all methods immediately—this can be deferred to its subclasses.
    Abstract Methods in Java

    Abstract methods serve the purpose of declaring methods in a superclass without providing an implementation. Subclasses are responsible for implementing these methods. Abstract methods are often referred to as having “subclass responsibility.”

    To declare an abstract method, use the following general syntax:

    abstract returnType methodName(parameterList);

    Since no method body is provided, any class that extends an abstract class must implement all of the abstract methods.

    Rules for Abstract Methods

    Some important rules associated with abstract methods are:

    • Any class containing one or more abstract methods must also be declared abstract.
    • You cannot combine the abstract modifier with the following modifiers: finalnativesynchronizedstaticprivate, or strictfp.
    Abstract Classes in Java

    An abstract class is a class that has partial implementation, meaning it may have methods that lack concrete definitions. To declare a class abstract, use the following syntax:

    abstract class ClassName {
        // class body
    }

    Abstract classes cannot be instantiated directly, and any class that extends an abstract class must either implement all abstract methods or be declared abstract itself.

    Example of Abstract Classes and Methods

    Here’s an example that demonstrates the use of abstract classes and methods:

    // Abstract class representing a general vehicle
    abstract class Vehicle {
        // Abstract method (no implementation)
        abstract void startEngine();
    
        // Concrete method
        void stopEngine() {
            System.out.println("Engine stopped.");
        }
    }
    
    // Concrete class representing a car
    class Car extends Vehicle {
        // Providing implementation for the abstract method
        void startEngine() {
            System.out.println("Car engine started.");
        }
    }
    
    // Driver class to demonstrate abstract classes
    public class Main {
        public static void main(String[] args) {
            Vehicle myCar = new Car();  // Vehicle reference, Car object
            myCar.startEngine();        // Output: Car engine started.
            myCar.stopEngine();         // Output: Engine stopped.
        }
    }

    Output:

    Car engine started.
    Engine stopped.

    Abstract Class in Java

    In Java, an abstract class is defined using the abstract keyword. It is a class that cannot be instantiated on its own and may contain both abstract and concrete methods (methods with bodies). The abstract keyword can only be applied to classes and methods, not variables. In this article, we will explore the concept of abstract classes and their use in Java.

    What is an Abstract Class?

    An abstract class is a blueprint for other classes and cannot be used to create objects directly. It can only be subclassed, allowing other classes to inherit its properties. Declaring an abstract class in Java requires using the abstract keyword in its class definition. This approach allows for partial implementation of functionality, leaving subclasses to complete the abstract methods.

    Illustration of Abstract Class

    abstract class Shape {
        int color;
        // Abstract method (no implementation)
        abstract void draw();
    }
    Important Points about Abstract Classes
    • Cannot Instantiate Abstract Classes: Instances of abstract classes cannot be created.
    • Constructors Are Allowed: Abstract classes can have constructors that are invoked when a subclass is instantiated.
    • No Abstract Methods Required: An abstract class can exist without any abstract methods.
    • Final Methods: Abstract classes can have final methods, but a method declared as abstract cannot be final, as this combination will result in an error.
    • Static Methods: Static methods can be defined in abstract classes.
    • Usage of Abstract Classes: Abstract classes can be used for both top-level (outer) and inner classes.
    • Incomplete Methods: If a subclass does not provide implementation for all abstract methods of a parent class, it must also be declared abstract.

    Examples of Java Abstract Classes

    1. Abstract Class with an Abstract Method: Here’s an example that demonstrates how an abstract class works in Java:

    // Abstract class
    abstract class Vehicle {
        abstract void displayDetails();
    }
    
    // Class extending the abstract class
    class Car extends Vehicle {
        void displayDetails() {
            String model = "Tesla";
            int year = 2024;
            double price = 55000.00;
    
            System.out.println("Model: " + model);
            System.out.println("Year: " + year);
            System.out.println("Price: $" + price);
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            Vehicle v = new Car();
            v.displayDetails();
        }
    }

    Output:

    Model: Tesla
    Year: 2024
    Price: $55000.0

    Examples of Java Abstract Classes

    2. Abstract Class with an Abstract Method : Here’s an example that demonstrates how an abstract class works in Java:

    // Abstract class
    abstract class Course {
        Course() {
            System.out.println("Enrolled in the course");
        }
        abstract void courseSyllabus();
        void study() {
            System.out.println("Studying for exams!");
        }
    }
    
    // Subclass extending the abstract class
    class ComputerScience extends Course {
        void courseSyllabus() {
            System.out.println("Topics: Data Structures, Algorithms, AI");
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            Course c = new ComputerScience();
            c.courseSyllabus();
            c.study();
        }
    }

    Output:

    Enrolled in the course
    Topics: Data Structures, Algorithms, AI
    Studying for exams!
    Key Observations

    Observation 1: Cannot Instantiate Abstract Classes

    An abstract class cannot be instantiated directly, but you can create references of the abstract class type.

    // Abstract class
    abstract class Animal {
        abstract void sound();
    }
    
    // Concrete class
    class Dog extends Animal {
        void sound() {
            System.out.println("Bark");
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            // Animal a = new Animal(); // Error: Cannot instantiate the abstract class
            Animal a = new Dog();
            a.sound();
        }
    }

    Output:

    Bark

    Observation 2: Abstract Class with Constructors

    A constructor in an abstract class can be called when an instance of a subclass is created.

    abstract class Appliance {
        Appliance() {
            System.out.println("Appliance Constructor");
        }
        abstract void use();
    }
    
    class WashingMachine extends Appliance {
        WashingMachine() {
            System.out.println("Washing Machine Constructor");
        }
        void use() {
            System.out.println("Washing clothes");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            WashingMachine wm = new WashingMachine();
            wm.use();
        }
    }

    Output:

    Appliance Constructor
    Washing Machine Constructor
    Washing clothes

    Observation 3: Abstract Class Without Abstract Methods

    Abstract classes can exist without having abstract methods.

    abstract class Library {
        void borrowBook() {
            System.out.println("Borrowing a book");
        }
    }
    
    class CityLibrary extends Library {}
    
    public class Main {
        public static void main(String[] args) {
            CityLibrary cl = new CityLibrary();
            cl.borrowBook();
        }
    }

    Output:

    Borrowing a book

    Control Abstraction in Java with Examples

    Before diving into control abstraction, let’s first understand the concept of abstraction.

    Abstraction: Abstraction simplifies the complexity of a system by exposing only the essential features while hiding the intricate internal details. For example, when driving a car, the driver interacts with the steering wheel, pedals, and other controls, but the complex workings of the engine and electronics are abstracted away. The driver only needs to know how to operate the car, not how every internal mechanism functions.

    Now, let’s explore an example of abstraction before delving into control abstraction:

    abstract class Person {
        abstract void displayDetails();
    }
    
    class Employee extends Person {
        void displayDetails() {
            String name = "John";
            int age = 30;
            double salary = 55000.50;
    
            System.out.println("Name: " + name);
            System.out.println("Age: " + age);
            System.out.println("Salary: $" + salary);
        }
    }
    
    class Main {
        public static void main(String[] args) {
            Person employee = new Employee();
            employee.displayDetails();
        }
    }

    Output:

    Name: John
    Age: 30
    Salary: $55000.5

    In the above example, the details of an employee are abstracted through the Person class, and the specifics are implemented in the Employee class. Only essential information is shown to the user.

    Types of Abstraction

    There are two main types of abstraction:

    1. Data Abstraction: This involves creating complex data types and exposing only essential operations.
    2. Control Abstraction: This focuses on simplifying the program logic by removing unnecessary execution details and structuring the program into manageable parts.

    Control Abstraction in Java

    Control Abstraction in programming refers to the process of using higher-level operations and constructs (such as functions, loops, and conditional statements) to manage and simplify complex control flows. Instead of repeatedly writing out specific instructions, control abstraction encourages modular and reusable code that follows the DRY (Don’t Repeat Yourself) principle.

    Key Features of Control Abstraction:
    • It promotes reusability by using methods and functions, thereby reducing code duplication.
    • Control abstraction bundles control statements into a single unit to make code easier to understand and manage.
    • It’s a fundamental feature of higher-level languages, including Java.
    • It focuses on how a task is achieved rather than the detailed steps involved in doing it.
    • It is often seen in structured programming through control structures like loops, conditionals, and function calls.

    Example of Control Abstraction:

    // Abstract class
    abstract class Vehicle {
        // Abstract method (does not have a body)
        public abstract void makeSound();
    
        // Regular method
        public void startEngine() {
            System.out.println("Engine starting...");
        }
    }
    
    // Subclass (inherit from Vehicle)
    class Car extends Vehicle {
        public void makeSound() {
            // The body of makeSound() is provided here
            System.out.println("Car sound: Vroom Vroom");
        }
    }
    
    class Main {
        public static void main(String[] args) {
            // Create a Car object
            Car myCar = new Car();
            myCar.startEngine();  // Regular method
            myCar.makeSound();    // Abstract method implementation
        }
    }

    Output:

    Engine starting...
    Car sound: Vroom Vroom

    In this example:

    • startEngine() is a regular method defined in the Vehicle abstract class, and it’s used by any subclass.
    • makeSound() is an abstract method in Vehicle, and each subclass (like Car) must provide its own implementation of this method.

    Key Steps of Control Flow:

    1. The necessary resources are obtained.
    2. The block of code is executed.
    3. When control exits the block, resources are released or closed.

    This structured approach ensures a clean flow of execution, making the program easier to read and maintain.

    Difference Between Data Hiding and Abstraction in Java

    Abstraction is the process of hiding the internal implementation and showcasing only the essential features or services. It allows the user to interact with the system without needing to understand its inner workings. This is accomplished through the use of abstract classes and interfaces in Java. Essentially, abstraction highlights only the necessary characteristics of an object, which distinguishes it from other objects, while suppressing non-essential details from the user.

    Real-Life Example of Abstraction:

    Consider an ATM machine. When you use an ATM, you see a graphical user interface (GUI) that displays services such as withdrawals, deposits, and checking balances. However, the internal mechanisms—such as how the transactions are processed—are hidden from the user.

    Types of Abstraction

    There are three main types of abstraction:

    1. Procedural Abstraction: Involves a series of procedures (or functions) that are executed sequentially to achieve abstraction through the use of classes and methods.
    2. Data Abstraction: Focuses on representing an object using a set of data while hiding its underlying implementation details.
    3. Control Abstraction: Involves writing a program in such a way that the control flow details (such as loops, conditions, or method calls) are hidden, encapsulating the operations into higher-level functions or objects.

    Advantages of Abstraction:
    • Security: Internal implementation details are hidden, which provides protection from unauthorized access.
    • Ease of Enhancement: Changes can be made to the internal system without affecting end users.
    • Flexibility: The system is easier to use for end users since they only interact with essential features.
    • Enhanced Application Quality: It helps in building more sophisticated and efficient applications by focusing on the most important aspects.
    Implementation of Abstraction in Java

    Abstraction is implemented using classes and interfaces, which represent only the significant traits and hide internal implementations. Here’s an example:

    // Abstract class representing a creature
    abstract class Creature {
    
        // Abstract method hiding specific details
        abstract void numberOfLegs();
    }
    
    // A class representing an Elephant, inheriting from Creature
    class Elephant extends Creature {
    
        // Implementing the abstract method
        void numberOfLegs() {
            System.out.println("The elephant has four legs.");
        }
    }
    
    // A class representing a Human, also inheriting from Creature
    class Human extends Creature {
    
        // Implementing the abstract method
        public void numberOfLegs() {
            System.out.println("Humans have two legs.");
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            // Creating Human object
            Human human = new Human();
            human.numberOfLegs();
    
            // Creating Elephant object
            Elephant elephant = new Elephant();
            elephant.numberOfLegs();
        }
    }

    Output:

    Humans have two legs.
    The elephant has four legs

    In this example, the Creature class is abstract and hides the internal details about the number of legs of different creatures. The concrete classes Elephant and Human provide specific implementations of the numberOfLegs method.

    Data Hiding in Java

    Data Hiding refers to the practice of hiding internal data from external access. This ensures that an object’s internal state cannot be directly accessed by other classes, maintaining security. Data hiding is usually achieved using access modifiers, such as private, which prevent external classes from directly accessing certain fields or methods.

    Example of Data Hiding:

    class BankAccount {
    
        // Private variable for account balance
        private double accountBalance;
    
        // Getter method to access account balance securely
        public double getAccountBalance() {
            return accountBalance;
        }
    
        // Setter method to update account balance securely
        public void setAccountBalance(double accountBalance) {
            this.accountBalance = accountBalance;
        }
    }

    In this example, the accountBalance variable is hidden from other classes by using the private access modifier. External classes can only access or modify the account balance through the public getter and setter methods, ensuring data protection.

    Key Differences Between Abstraction and Data Hiding:
    • Abstraction focuses on hiding unnecessary implementation details and showing only essential features to the user.
    • Data Hiding restricts access to the internal data of a class, ensuring that only certain methods can access or modify it.

    Both concepts work together to promote encapsulation, ensuring the security and integrity of the system’s internal workings.

  • Inheritance

    Inheritance and Constructors

    In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.

    Note:

    If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.

    Example:

    // Java Program to Demonstrate
    // Constructor Invocation Without Using super Keyword
    
    // Parent class
    class Parent {
        // Constructor of the parent class
        Parent() {
            System.out.println("Parent Class Constructor Called");
        }
    }
    
    // Child class
    class Child extends Parent {
        // Constructor of the child class
        Child() {
            System.out.println("Child Class Constructor Called");
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            // Creating an object of the child class
            Child childObj = new Child();
    
            // Note: The constructor of the parent class is invoked first,
            // followed by the constructor of the child class.
        }
    }

    Output:

    Parent Class Constructor Called
    Child Class Constructor Called

    In Java, constructors are used to initialize the attributes of an object, making the language more reflective of real-world scenarios. By default, Java provides a no-argument constructor that is automatically invoked if no constructors are defined. However, if you create a custom constructor, such as a parameterized one, you must explicitly define the default constructor, as it will no longer be provided automatically.

    Note:

    If a base class has a no-argument constructor, it is called automatically when a constructor of the derived class is invoked.

    Example:

    // Java Program to Demonstrate
    // Constructor Invocation Using super Keyword
    
    // Base class
    class Vehicle {
        int speed;
    
        // Parameterized constructor of the base class
        Vehicle(int spd) {
            speed = spd;
        }
    }
    
    // Subclass
    class Car extends Vehicle {
        String model;
    
        // Constructor of the subclass
        Car(int spd, String mdl) {
            // Using super to call the base class constructor
            super(spd);
            model = mdl;
        }
    
        // Method to display details
        void displayDetails() {
            System.out.println("Speed: " + speed + ", Model: " + model);
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            // Creating an object of the subclass
            Car carObj = new Car(120, "Sedan");
    
            // Displaying details of the car
            carObj.displayDetails();
        }
    }

    Output:

    Speed: 120, Model: Sedan

    Explanation:

    In this example, the super() keyword is used to call the base class (Vehicle) constructor with a parameter (speed). This call must be the first statement in the subclass (Car) constructor. Afterward, the model attribute is initialized, and the displayDetails() method prints the values of speed and model.

    Key Points:

    • The base class constructor is always called before the subclass constructor.
    • The super() keyword is used to explicitly invoke the base class constructor and must be the first line in the subclass constructor.
    • If super() is not used, the default constructor of the base class is invoked automatically.

    Multiple Inheritance

    Multiple inheritance is an object-oriented concept where a class can inherit from more than one parent class. However, a problem arises when both parent classes have methods with the same signature. In such cases, the compiler is unable to decide which method to execute, leading to ambiguity. Java avoids these complexities by not supporting multiple inheritance through classes.

    Note:

    Java does not support multiple inheritance through classes, but it handles this scenario for interfaces using default methods introduced in Java 8.

    Example 1:

    // Java Program to Demonstrate Lack of Support for Multiple Inheritance
    
    // First parent class
    class Parent1 {
        void display() {
            System.out.println("Parent1");
        }
    }
    
    // Second parent class
    class Parent2 {
        void display() {
            System.out.println("Parent2");
        }
    }
    
    // Class trying to inherit from both Parent1 and Parent2 (Invalid)
    class Child extends Parent1, Parent2 {
        public static void main(String[] args) {
            Child obj = new Child();
            obj.display(); // Ambiguity issue
        }
    }

    Output:

    Compilation Error

    Explanation:

    Java does not support multiple inheritance using classes. If you try to inherit from both Parent1 and Parent2, the compiler will throw an error because it cannot determine which display() method to call.

    How Java Handles Multiple Inheritance with Interfaces

    While Java doesn’t allow multiple inheritance through classes, it allows it through interfaces. Java 8 introduced default methods, which allow interfaces to provide default implementations of methods. If a class implements multiple interfaces with default methods that have the same signature, it must resolve the conflict by overriding the method.

    Example 2: Handling Multiple Inheritance with Interfaces

    // Interface 1
    interface Interface1 {
        default void show() {
            System.out.println("Default Interface1");
        }
    }
    
    // Interface 2
    interface Interface2 {
        default void show() {
            System.out.println("Default Interface2");
        }
    }
    
    // Implementation class that implements both interfaces
    class MyClass implements Interface1, Interface2 {
    
        // Overriding the show method to resolve ambiguity
        @Override
        public void show() {
            Interface1.super.show(); // Explicitly calling Interface1's show method
            Interface2.super.show(); // Explicitly calling Interface2's show method
        }
    
        // Methods to call specific interface methods
        public void showInterface1() {
            Interface1.super.show();
        }
    
        public void showInterface2() {
            Interface2.super.show();
        }
    
        public static void main(String[] args) {
            MyClass obj = new MyClass();
            obj.show(); // Resolving ambiguity
            System.out.println("Calling individual interface methods:");
            obj.showInterface1();
            obj.showInterface2();
        }
    }

    Output:

    Default Interface1
    Default Interface2
    Calling individual interface methods:
    Default Interface1
    Default Interface2

    Explanation:

    In this example, MyClass implements both Interface1 and Interface2, which both have a default show() method. By overriding the show() method in MyClass, we use Interface1.super.show() and Interface2.super.show() to specify which interface’s method to call. Additionally, the class provides methods to call individual interface methods if needed.

    Example 3: Diamond Problem with Default Methods in Interfaces

    // Root interface with a default method
    interface RootInterface {
        default void display() {
            System.out.println("Default RootInterface");
        }
    }
    
    // First child interface extending RootInterface
    interface ChildInterface1 extends RootInterface {
    }
    
    // Second child interface extending RootInterface
    interface ChildInterface2 extends RootInterface {
    }
    
    // Class implementing both child interfaces
    class MyClass implements ChildInterface1, ChildInterface2 {
        public static void main(String[] args) {
            MyClass obj = new MyClass();
            obj.display(); // No ambiguity, RootInterface's method is called
        }
    }

    Output:

    Default RootInterface

    Explanation:

    In this case, both ChildInterface1 and ChildInterface2 extend the same RootInterface, which has a default display() method. Since neither ChildInterface1 nor ChildInterface2 overrides the method, there is no ambiguity, and the RootInterface method is called directly.

    Interfaces and Inheritance in Java

    In Java, a class can extend another class and implement one or more interfaces. This relationship has a significant role in understanding Java’s approach to Multiple Inheritance.

    Key Concepts:

    1. Inheritance Hierarchy: Java enforces a clear hierarchy, where a class can inherit from another class using the extends keyword, and implement interfaces using the implements keyword. The hierarchy cannot be reversed; that is, a class cannot extend an interface, as doing so would violate the core principles of class-based inheritance.

    Example of Class Implementing Multiple Interfaces

    // Java program to demonstrate that a class can
    // implement multiple interfaces
    interface InterfaceA {
        void method1();
    }
    
    interface InterfaceB {
        void method2();
    }
    
    // class implements both interfaces
    // and provides implementation for the methods
    class ExampleClass implements InterfaceA, InterfaceB {
        @Override
        public void method1() {
            System.out.println("Executing method1 from InterfaceA");
        }
    
        @Override
        public void method2() {
            System.out.println("Executing method2 from InterfaceB");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            ExampleClass obj = new ExampleClass();
    
            // calling the methods implemented in the class
            obj.method1();
            obj.method2();
        }
    }

    2. Interface InheritanceIn Java, an interface can extend another interface, allowing for inheritance of method signatures, which can later be implemented in classes.

    Example of Interface Inheritance

    // Interface inheritance demonstration
    interface InterfaceA {
        void display();
    }
    
    interface InterfaceB extends InterfaceA {
        void show();
    }
    
    class DemoClass implements InterfaceB {
        @Override
        public void display() {
            System.out.println("Display method from InterfaceA");
        }
    
        @Override
        public void show() {
            System.out.println("Show method from InterfaceB");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            DemoClass obj = new DemoClass();
            obj.display();
            obj.show();
        }
    }

    Output:

    Display method from InterfaceA
    Show method from InterfaceB
    Types of Inheritance in Java

    Java supports different types of inheritance, such as single, multilevel, and hierarchical inheritance, while restricting multiple and hybrid inheritance through interfaces.

    1. Single Inheritance: When a class inherits from only one superclass.

    class A {
        int num1;
        void setNum1(int x) {
            num1 = x;
        }
    }
    
    class B extends A {
        int num2;
        void setNum2(int y) {
            num2 = y;
        }
    
        void displayProduct() {
            System.out.println("Product: " + (num1 * num2));
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            B obj = new B();
            obj.setNum1(3);
            obj.setNum2(4);
            obj.displayProduct();
        }
    }

    Output:

    Product: 12

    2. Multilevel Inheritance: Involves a chain of inheritance where a class is derived from another derived class.

    class A {
        int num1;
        void setNum1(int x) {
            num1 = x;
        }
    }
    
    class B extends A {
        int num2;
        void setNum2(int y) {
            num2 = y;
        }
    }
    
    class C extends B {
        void displaySum() {
            System.out.println("Sum: " + (num1 + num2));
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            C obj = new C();
            obj.setNum1(5);
            obj.setNum2(10);
            obj.displaySum();
        }
    }

    Output:

    Sum: 15

    3. Hierarchical Inheritance: Multiple classes inherit from the same superclass.

    class A {
        void greet() {
            System.out.println("Hello from Class A");
        }
    }
    
    class B extends A {
        void greetFromB() {
            System.out.println("Greetings from Class B");
        }
    }
    
    class C extends A {
        void greetFromC() {
            System.out.println("Greetings from Class C");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            B objB = new B();
            C objC = new C();
    
            objB.greet();
            objB.greetFromB();
    
            objC.greet();
            objC.greetFromC();
        }
    }

    Output:

    Hello from Class A
    Greetings from Class B
    Hello from Class A
    Greetings from Class C
    Inheritance in Interfaces

    Java interfaces can inherit from multiple interfaces, enabling classes to implement complex behavior while avoiding ambiguity.

    Example of Multiple Interface Inheritance
    interface InterfaceX {
        void displayName();
    }
    
    interface InterfaceY {
        void displayDepartment();
    }
    
    interface InterfaceZ extends InterfaceX, InterfaceY {
        void displayRole();
    }
    
    class Employee implements InterfaceZ {
        @Override
        public void displayName() {
            System.out.println("Employee Name: John");
        }
    
        @Override
        public void displayDepartment() {
            System.out.println("Department: IT");
        }
    
        @Override
        public void displayRole() {
            System.out.println("Role: Developer");
        }
    
        public static void main(String[] args) {
            Employee emp = new Employee();
            emp.displayName();
            emp.displayDepartment();
            emp.displayRole();
        }
    }

    Output:

    Employee Name: John
    Department: IT
    Role: Developer

    Association, Composition and Aggregation in Java

    In object-oriented programming, relationships between classes are fundamental for defining interactions between objects. Java, as an object-oriented language, allows modeling these relationships using association, aggregation, and composition. These concepts describe how instances of classes relate and interact with one another.

    Association

    Association represents a general relationship between two independent classes, where one class may use or interact with another. This relationship can be one-to-one, one-to-many, many-to-one, or many-to-many, without implying ownership. Classes in association are independent of each other.

    Types of Association

    • Unidirectional Association: One class knows and interacts with another, but the reverse is not true. For example, a Customer class may be associated with an Order class, but the Order class does not need to be aware of the Customer.
    • Bidirectional Association: Both classes are aware of and interact with each other. For example, a Player class and a Team class might be associated in a bidirectional relationship, where a player belongs to a team, and the team knows which players are on it.

    Example of Association:

    // Java program illustrating Association
    
    import java.util.*;
    
    class Library {
        private String libraryName;
        private Set<Book> books;
    
        public Library(String libraryName) {
            this.libraryName = libraryName;
        }
    
        public String getLibraryName() {
            return this.libraryName;
        }
    
        public void setBooks(Set<Book> books) {
            this.books = books;
        }
    
        public Set<Book> getBooks() {
            return this.books;
        }
    }
    
    class Book {
        private String title;
    
        public Book(String title) {
            this.title = title;
        }
    
        public String getTitle() {
            return this.title;
        }
    }
    
    public class AssociationExample {
        public static void main(String[] args) {
            // Creating Book objects
            Book book1 = new Book("Java Basics");
            Book book2 = new Book("Advanced Java");
    
            // Adding books to a set
            Set<Book> bookSet = new HashSet<>();
            bookSet.add(book1);
            bookSet.add(book2);
    
            // Creating a Library object
            Library library = new Library("City Library");
    
            // Setting books for the library
            library.setBooks(bookSet);
    
            // Displaying library books
            for (Book book : library.getBooks()) {
                System.out.println(book.getTitle() + " is available at " + library.getLibraryName());
            }
        }
    }

    Output:

    Java Basics is available at City Library
    Advanced Java is available at City Library

    In this example, the Library and Book classes are associated, where a library contains multiple books, forming a one-to-many relationship.

    Aggregation

    Aggregation is a specialized form of association that represents a “has-a” relationship. In aggregation, one class (the whole) contains other classes (the parts), but the lifecycle of the parts is independent of the whole. For instance, a University may contain multiple Departments, but a department can exist independently of the university.

    Example of Aggregation:

    // Java program illustrating Aggregation
    
    import java.util.*;
    
    class Course {
        private String courseName;
        private int courseId;
    
        public Course(String courseName, int courseId) {
            this.courseName = courseName;
            this.courseId = courseId;
        }
    
        public String getCourseName() {
            return this.courseName;
        }
    
        public int getCourseId() {
            return this.courseId;
        }
    }
    
    class Department {
        private String deptName;
        private List<Course> courses;
    
        public Department(String deptName, List<Course> courses) {
            this.deptName = deptName;
            this.courses = courses;
        }
    
        public List<Course> getCourses() {
            return this.courses;
        }
    }
    
    class University {
        private String universityName;
        private List<Department> departments;
    
        public University(String universityName, List<Department> departments) {
            this.universityName = universityName;
            this.departments = departments;
        }
    
        public int getTotalCoursesInUniversity() {
            int totalCourses = 0;
            for (Department dept : departments) {
                totalCourses += dept.getCourses().size();
            }
            return totalCourses;
        }
    }
    
    public class AggregationExample {
        public static void main(String[] args) {
            // Creating Course objects
            Course c1 = new Course("Data Structures", 101);
            Course c2 = new Course("Algorithms", 102);
            Course c3 = new Course("Operating Systems", 103);
    
            // Creating lists of courses
            List<Course> csCourses = Arrays.asList(c1, c2);
            List<Course> itCourses = Arrays.asList(c3);
    
            // Creating Department objects
            Department csDept = new Department("Computer Science", csCourses);
            Department itDept = new Department("Information Technology", itCourses);
    
            // Creating a list of departments
            List<Department> departments = Arrays.asList(csDept, itDept);
    
            // Creating a University object
            University university = new University("Tech University", departments);
    
            // Printing total courses in university
            System.out.println("Total courses in university: " + university.getTotalCoursesInUniversity());
        }
    }

    Output:

    Total courses in university: 3

    In this example, a university contains multiple departments, each having courses. The university and department are in an aggregation relationship, where departments can exist independently of the university.

    Composition

    Composition is a strong form of association where the lifecycle of the contained objects is dependent on the container object. If the container object is destroyed, the contained objects are also destroyed. This represents a “part-of” relationship, like a Car and its Engine. If the car is destroyed, the engine also ceases to exist.

    Example of Composition:

    // Java program illustrating Composition
    
    import java.util.*;
    
    class Engine {
        private String engineType;
    
        public Engine(String engineType) {
            this.engineType = engineType;
        }
    
        public String getEngineType() {
            return this.engineType;
        }
    }
    
    class Car {
        private String carName;
        private Engine engine;
    
        public Car(String carName, String engineType) {
            this.carName = carName;
            this.engine = new Engine(engineType); // Composition
        }
    
        public String getCarDetails() {
            return this.carName + " has an engine of type " + engine.getEngineType();
        }
    }
    
    public class CompositionExample {
        public static void main(String[] args) {
            // Creating a Car object with composition
            Car car = new Car("Tesla", "Electric");
    
            // Displaying car details
            System.out.println(car.getCarDetails());
        }
    }

    Output:

    Tesla has an engine of type Electric

    In this example, the Car and Engine have a composition relationship. The Engine cannot exist without the Car, representing strong ownership.

    Key Differences Between Association, Aggregation, and Composition
    FeatureAssociationAggregationComposition
    DefinitionGeneral relationship between two classes“Has-a” relationship“Part-of” relationship
    DependencyClasses can exist independentlyParts can exist independently of the wholeParts depend on the whole
    LifecycleIndependent lifecyclesIndependent lifecyclesDependent lifecycles
    OwnershipNo ownership impliedShared ownershipExclusive ownership
    StrengthWeakModerateStrong

    These relationships play a key role in designing well-structured, reusable, and maintainable software systems.

  • OOPS in Java

    Object Oriented Programming (OOPs) Concept in Java

    Object-Oriented Programming (OOP) in Java refers to a programming approach where objects are used to represent and execute the code’s functionality. Objects are the core components, performing specific tasks as defined by the programmer.

    OOP is designed to model real-world entities, such as inheritance, encapsulation, polymorphism, etc., in code. The primary objective is to bind data and the functions that operate on the data, ensuring that no other part of the program can directly access this data except through those functions.

    Key OOP Concepts in Java

    Java is built on the foundation of Object-Oriented Programming. Understanding key OOP concepts such as inheritance, encapsulation, abstraction, and polymorphism is critical to writing scalable and maintainable Java code.

    Prerequisites

    Before diving into the pillars of OOP, let’s review the essentials of method declaration and message passing in Java.

    Method Declaration

    A method in Java consists of six main components:

    1. Access Modifier: Defines the accessibility of the method within the application. The common types are:

    • public: Accessible in all classes.
    • protected: Accessible within the package and in subclasses.
    • private: Accessible only within the class.
    • Default (no modifier): Accessible only within the same package.

    2. Return Type: Specifies the data type returned by the method, or void if no value is returned.
    3. Method Name: Follows the same naming conventions as fields, but typically starts with a lowercase letter.
    4. Parameter List: The input parameters (if any), defined as a comma-separated list within parentheses.
    5. Exception List: Specifies any exceptions that the method might throw.
    6. Method Body: Contains the block of code that is executed when the method is called.

    Message Passing

    Objects in Java communicate with one another by sending messages, which involve calling methods. A message consists of the object’s name, the method to be executed, and any required information.

    Java OOP Concepts

    OOP in Java is centered around several key concepts:

    1. Class: A class is a blueprint or prototype that defines the properties and behaviors common to all objects of a specific type. A class typically consists of fields (attributes) and methods (behaviors). In Java, a class can be defined as:

    public class Employee {
        // Fields
        String name;
        int salary;
    
        // Methods
        void setDetails(String n, int s) {
            name = n;
            salary = s;
        }
    
        void displayDetails() {
            System.out.println("Name: " + name);
            System.out.println("Salary: " + salary);
        }
    }

    2. Object: An object is an instance of a class. It represents a specific entity in a Java program, with its own state and behavior.

    public class Main {
        public static void main(String[] args) {
            Employee emp = new Employee();
            emp.setDetails("John Doe", 50000);
            emp.displayDetails();
        }
    }

    Output:

    Name: John Doe
    Salary: 50000
    The Four Pillars of OOP

    1. Abstraction: Abstraction involves hiding unnecessary details and showing only the essential features of an object. In Java, abstraction is achieved using abstract classes and interfaces.

    Example of an abstract class:

    abstract class Vehicle {
        abstract void start();
    }
    
    class Car extends Vehicle {
        void start() {
            System.out.println("Car is starting");
        }
    }

    2. Encapsulation: Encapsulation is the practice of wrapping data and methods into a single unit (class). It restricts direct access to the data by making the fields private and providing public getter and setter methods to modify and access them.

    class Person {
        private String name;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }

    3. Inheritance: Inheritance allows a class to inherit properties and methods from another class. It promotes reusability and a hierarchical class structure. Inheritance is achieved using the extends keyword.

    class Animal {
        void sound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        void sound() {
            System.out.println("Dog barks");
        }
    }

    4. Polymorphism: Polymorphism allows methods or objects to behave in different ways based on the context. It is of two types: compile-time (method overloading) and runtime (method overriding).

    Method Overloading (Compile-time Polymorphism):

    class Calculator {
        int add(int a, int b) {
            return a + b;
        }
    
        int add(int a, int b, int c) {
            return a + b + c;
        }
    }
    Advantages of OOP over Procedural Programming
    • Code Reusability: By using classes and objects, you can reuse code efficiently without repetition.
    • Code Organization: Object-oriented code is better structured, making it easier to understand and maintain.
    • DRY Principle: OOP promotes the “Don’t Repeat Yourself” principle by allowing common code to be reused.
    • Faster Development: OOP speeds up development by encouraging modular components that can be reused across projects.

    Java Classes and Objects

    In Java, classes and objects are core concepts of Object-Oriented Programming (OOP), used to represent real-world entities and behaviors. A class acts as a blueprint for creating objects, while an object is an instance of a class. For example, “Dog” can be considered a class, while a specific dog named “Bruno” would be an object of that class.

    In this article, we’ll explore Java classes, objects, and how to implement them in your programs.

    Java Classes

    class in Java is a collection of objects with common properties and behaviors. It is essentially a user-defined prototype from which objects are created. For instance, “Car” is a class, while “BMW” is an object.

    Properties of Java Classes

    • A class is not a physical entity but a blueprint used to create objects.
    • A class itself does not occupy memory; only objects do.
    • It groups variables (attributes) and methods (behaviors) under a common structure.

    A class in Java can include the following components:

    • Data members (variables)
    • Methods
    • Constructors
    • Nested Classes
    • Interfaces

    Class Declaration in Java

    access_modifier class ClassName {
        // variables (data members)
        // methods
        // constructors
        // nested classes
        // interfaces
    }

    In this example, the Student class has two data members, id and name, with default values 0 and null, respectively.

    Components of Java Classes

    A class declaration typically contains the following:

    • Modifiers: A class can be public or have default access.
    • Class keyword: Used to declare a class.
    • Class name: Starts with an uppercase letter by convention.
    • Superclass: The parent class (if any), specified with the extends keyword.
    • Interfaces: Any implemented interfaces, separated by commas and declared with the implements keyword.
    • Body: The class body is enclosed in curly braces {}.
    Constructors

    Constructors initialize new objects. They have the same name as the class and no return type. Fields (data members) define the state of objects, while methods define the behavior.

    Java Objects

    An object in Java is an instance of a class, representing a specific entity or concept. Each object has:

    • State: Represented by the attributes (data members) of the object.
    • Behavior: Represented by the methods of the object.
    • Identity: A unique identifier that allows interaction between different objects.

    For example, in a shopping system, objects could include “ShoppingCart”, “Product”, and “Customer”.

    Declaring and Initializing Java Objects

    When an object is created, the class is instantiated. The values of the object’s attributes (state) are unique for each instance.

    Dog myDog; // Declaration
    myDog = new Dog(); // Instantiation

    Example of an Object Initialization

    public class Dog {
        // Instance variables
        String name;
        String breed;
        int age;
        String color;
    
        // Constructor
        public Dog(String name, String breed, int age, String color) {
            this.name = name;
            this.breed = breed;
            this.age = age;
            this.color = color;
        }
    
        @Override
        public String toString() {
            return "My dog's name is " + name + ", breed: " + breed + ", age: " + age + ", color: " + color;
        }
    
        public static void main(String[] args) {
            Dog myDog = new Dog("Buddy", "Golden Retriever", 3, "Golden");
            System.out.println(myDog.toString());
        }
    }

    Output:

    My dog's name is Buddy, breed: Golden Retriever, age: 3, color: Golden
    Memory Allocation in Java Objects

    When an object is created, it is allocated memory on the heap. All classes have at least one constructor; if none is explicitly defined, Java provides a default constructor.

    Methods to Create Objects

    There are several ways to create objects in Java:

    1. Using the new keyword:

    Car myCar = new Car();

    2. Using Class.forName(String className):

    Car obj = (Car)Class.forName("com.example.Car").newInstance();

    3. Using clone() method:

    Car car1 = new Car();
    Car car2 = (Car)car1.clone();

    4. Using Deserialization:

    ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
    Car car = (Car)in.readObject();
    Anonymous Objects

    Anonymous objects are objects created without a reference and are often used for immediate method calls. These objects are destroyed after the method call completes.

    new Car().drive();
    Difference Between Class and Object
    ClassObject
    A blueprint for creating objects.An instance of the class.
    Does not occupy memory.Occupies memory when instantiated.
    Logical entity.Physical entity.
    Declared only once.Multiple objects can be created from the same class.

    Java Naming Conventions

    In software development, especially in Java, writing clean and readable code is essential. One key aspect of this is following proper naming conventions. This practice ensures that your code is easy to read and understand, both for yourself and for other developers who may work on the same project. While at a smaller scale these conventions may not seem critical, they become increasingly important in large, industrial-level projects where maintaining clean, organized code can significantly reduce development time and confusion.

    For example, when naming a variable meant to store displacement, a meaningful name like “displacement” should be used, rather than something unclear like “x” or “d”. As the project grows, meaningful names enhance readability and reduce errors.

    Key Naming Guidelines
    • Classes: When naming a class, it should be a noun, reflecting the purpose it serves in the program. The name should describe what the class is about without needing to read its contents. Examples of good class names include AddNumbers, ReverseString, etc. Poor names would be generic or meaningless like A1 or Programming.
    • Interfaces: Interfaces usually describe capabilities and are often named with adjectives. For example, common Java interfaces like Runnable and Serializable describe behavior. It’s also a good practice to use the suffix “able” (e.g., Readable), though it’s not mandatory.
    • Methods: Methods, being actions, should be named as verbs. Their names should clearly describe what they do. For example, the main() method in Java signifies the starting point of the program and is a good method name conventionally.
    • Constants: Constants are fixed values and should be named in uppercase with words separated by underscores (_). Examples include PI, MAX_VALUE, MIN_VALUE.
    Java Naming Conventions Overview

    In Java, it is considered best practice to name classes, variables, and methods according to their intended functionality. This approach enhances the readability and maintainability of the code. Java follows the CamelCase convention for naming classes, methods, and variables, where words are written together without spaces, and each word after the first starts with an uppercase letter. For example, firstName or calculateArea.

    However, there are a few exceptions:

    • Packages: Package names should be written in all lowercase, even if they contain multiple words.
    • Constants: Constants should be written in uppercase, with words separated by underscores.
    Naming Conventions for Specific Java Elements

    1. Classes and Interfaces

    • Classes: Class names should be nouns, using CamelCase, with each internal word starting with a capital letter. Avoid abbreviations or acronyms, and instead use meaningful words.Example: StudentIntegerDataProcessor
    • Interfaces: Interfaces should follow the same convention as classes, but should often reflect actions or behaviors, typically in the form of adjectives.Example: RunnableSerializable

    2. Methods

    • Method Names: Methods should be verbs, following the camelCase convention. The first word is in lowercase, and each subsequent word starts with an uppercase letter.Example: calculateTotalfindMaximumprintDetails

    3. Variables

    • Variable Names: Variables should have meaningful names and be short but descriptive. They should not start with an underscore (_) or dollar sign ($), even though Java permits it. Single-character variable names (like ijk) should generally be reserved for temporary variables in loops or similar short-term uses.Example: int scoredouble interestRateString username

    4. Constants

    • Constant Names: Constants are named in all uppercase letters, with words separated by underscores (_).Example: MAX_LIMITPIDEFAULT_TIMEOUT

    5. Packages

    • Package Names: Package names should always be written in lowercase, with each part separated by dots (.). The first part of the package name should usually be the top-level domain name (e.g., comorg), followed by the company’s domain and additional parts.Example: com.example.projectnameorg.utilities.math
    Importance of Naming Conventions

    Adopting proper naming conventions improves code clarity, making it easier for other developers to understand the purpose of each class, method, variable, and constant without having to examine the underlying implementation. Following these guidelines also helps avoid confusion and errors in larger codebases. Furthermore, adhering to established naming conventions is a sign of professionalism and helps teams maintain a consistent coding style across projects.

    These conventions, though simple, are critical in producing clean, readable, and maintainable Java code—allowing teams to work more effectively and reducing the chances of misinterpretation or mistakes during development.

    Methods in Java

    method in Java is a collection of statements designed to perform a specific task and, optionally, return a result. Methods can also execute tasks without returning any value. By using methods, Java allows code reuse, eliminating the need to rewrite similar logic multiple times. Unlike languages such as C, C++, or Python, every method in Java must belong to a class.

    Key Points about Methods:
    • A method is similar to a function and is used to describe the behavior of an object.
    • It is a block of code that performs a particular task.

    Syntax of a Method:

    <access_modifier> <return_type> <method_name>(list_of_parameters) {
        // body of the method
    }
    Advantages of Methods:
    • Code Reusability: Methods allow code to be written once and reused as needed, saving time and effort.
    • Code Optimization: Methods simplify complex operations and improve the structure of the code.
    Method Declaration

    A method declaration generally has six components:

    1. Modifier: Defines the access level of the method (e.g., public, private, protected, default).

    • public: Accessible in all classes.
    • protected: Accessible within the class and its subclasses.
    • private: Accessible only within the defining class.
    • default: Accessible within the same class and package, with no modifier.

    2. Return Type: Specifies the data type of the value returned by the method or void if no value is returned.
    3. Method Name: The name given to the method, following the same rules as variable names but typically being a verb. It is required.
    4. Parameter List: A comma-separated list of input parameters (preceded by their data types). If no parameters are needed, empty parentheses are used. This is optional.
    5. Exception List: Specifies any exceptions the method can throw. This is optional.
    Method Body: The actual code executed by the method, enclosed in braces. This is also optional.

    Types of Methods in Java

    1. Predefined Methods: These are methods already provided by Java libraries, also known as standard or built-in methods. We can directly call these methods within our program.

    2. User-defined Methods: Methods written by the programmer, tailored to specific tasks according to the program’s needs.

    Ways to Create Methods in Java

    1. Instance Methods: Access instance data using the object name. These methods are declared within a class.

    void methodName() {
        // method body
    }

    2. Static Methods: Access static data using the class name. These methods are declared with the static keyword.

    static void methodName() {
        // method body
    }
    Method Signature

    A method signature consists of the method name and its parameter list (the number, type, and order of parameters). The return type and exceptions are not part of the method signature.

    Example:

    int max(int x, int y)  // Method signature

    In this example, the method max has two parameters of type int.

    Naming a Method

    The convention for naming methods in Java is to use verbs in lowercase or multi-word names starting with a verb. If the name contains multiple words, the first letter of each word after the first should be capitalized (CamelCase).

    Rules:

    • Method names should be verbs and start with a lowercase letter.
    • If the method name contains more than one word, the first word must be a verb, followed by an adjective or noun.
    • In multi-word names, capitalize the first letter of each subsequent word (e.g., findMaxcomputeSum).
    Method Calling

    Methods need to be called to execute. They can be called in three situations:

    • The method completes all its statements.
    • The method reaches a return statement.
    • The method throws an exception.

    Example:

    class Calculator {
        public int add(int a, int b) {
            return a + b;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Calculator calc = new Calculator();
            int result = calc.add(5, 10);
            System.out.println("Sum: " + result);
        }
    }

    Output:

    Sum: 15
    Passing Parameters to Methods

    Parameters can be passed in two ways:

    1. Passing Arrays: Arrays can be passed as arguments to methods.

    2. Varargs (Variable Arguments): When the number of parameters is unknown, we can use the varargs feature by specifying the parameter type followed by an ellipsis (...).

    Method Overloading

    Method overloading allows multiple methods with the same name but different parameter lists to coexist in the same class. This enables methods to handle various types of inputs.

    Memory Allocation for Methods

    Method calls are implemented using a stack. When a method is invoked, a stack frame is created to store its arguments, local variables, and return value. Once the method completes, its stack frame is removed from the stack.

    Example:

    class Example {
        private int number;
        private String name;
    
        // Getter and Setter methods
        public int getNumber() { return number; }
        public String getName() { return name; }
    
        public void setNumber(int number) { this.number = number; }
        public void setName(String name) { this.name = name; }
    
        // Other methods
        public void printDetails() {
            System.out.println("Number: " + number);
            System.out.println("Name: " + name);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Example example = new Example();
            example.setNumber(42);
            example.setName("Java Example");
            example.printDetails();
        }
    }

    Output:

    Number: 42
    Name: Java Example

    Access Modifiers in Java

    Access modifiers in Java are essential for controlling the visibility and accessibility of classes, constructors, methods, variables, and data members. They provide a way to enforce security and manage how components of a program can interact with each other based on the access modifier used. In this section, we’ll explore the types of access modifiers available in Java, along with examples that demonstrate their use.

    Types of Access Modifiers in Java

    There are four access modifiers in Java:

    1. Default (No keyword required)
    2. Private
    3. Protected
    4. Public

    1. Default Access Modifier

    If no access modifier is specified, the default modifier is used. Members with the default access modifier are accessible only within the same package.

    Example:

    // Java program demonstrating default access modifier
    package package1;
    
    class ExampleClass {
        void showMessage() {
            System.out.println("This is a default access modifier example.");
        }
    }
    // Attempting to access a default class from another package
    package package2;
    
    import package1.*;
    
    public class TestClass {
        public static void main(String[] args) {
            ExampleClass example = new ExampleClass(); // Compile-time error
            example.showMessage();
        }
    }

    Output:

    Compile-time error

    2. Private Access Modifier

    The private keyword is used to restrict access to class members. Members declared as private are accessible only within the class where they are defined.

    Example:

    // Java program illustrating private access modifier
    package package1;
    
    class ExampleClass {
        private void displayMessage() {
            System.out.println("This is a private method.");
        }
    }
    
    public class TestClass {
        public static void main(String[] args) {
            ExampleClass example = new ExampleClass();
            example.displayMessage();  // Compile-time error
        }
    }

    Output:

    error: displayMessage() has private access in ExampleClass
            example.displayMessage();

    3. Protected Access Modifier

    The protected keyword allows access to members within the same package and also in subclasses, even if they are in different packages.

    Example:

    // Package 1 - Parent class with protected method
    package package1;
    
    public class ParentClass {
        protected void displayMessage() {
            System.out.println("This is a protected method.");
        }
    }
    // Package 2 - Subclass inheriting from ParentClass
    package package2;
    
    import package1.ParentClass;
    
    public class ChildClass extends ParentClass {
        public static void main(String[] args) {
            ChildClass child = new ChildClass();
            child.displayMessage();
        }
    }

    Output:

    This is a protected method.

    4. Public Access Modifier

    The public keyword gives the widest access. Members declared as public can be accessed from any class, in any package.

    Example:

    // Java program demonstrating public access modifier
    package package1;
    
    public class ExampleClass {
        public void showMessage() {
            System.out.println("This is a public method.");
        }
    }
    // Accessing a public method from another package
    package package2;
    
    import package1.ExampleClass;
    
    public class TestClass {
        public static void main(String[] args) {
            ExampleClass example = new ExampleClass();
            example.showMessage();
        }
    }

    Output:

    This is a public method.
    Key Points:
    • Default access is package-private, meaning it’s accessible only within the same package.
    • Private access is the most restrictive, limiting access only to the class in which the member is declared.
    • Protected access allows visibility in the same package and in subclasses across different packages.
    • Public access provides unrestricted access throughout the program.
    Best Practices for Using Access Modifiers
    • Always prefer the most restrictive access level for class members. For example, use private unless there’s a specific reason to expose a member.
    • Avoid using public fields, as this breaks encapsulation. Instead, use getter and setter methods to control access to fields.
    Algorithm for Using Access Modifiers in Java

    1. Define the class: Create a class representing your object or functionality.
    2. Declare instance variables: Define the data members that represent the object’s state.
    3. Choose access modifiers:

    • Use private for variables that should not be accessible outside the class.
    • Use protected for variables that can be accessed by subclasses, even in different packages.
    • Use public for variables that need to be accessed from any part of the program.
      Implement getter and setter methods: Even for public variables, it’s good practice to use accessors and mutators to control how values are accessed and modified.

    4. Test visibility: Ensure that your chosen access levels make sense for the structure and interaction of your classes.

    Java Constructors

    In Java, a constructor is a special type of method used to initialize an object. Constructors are called when an object of a class is created, allowing developers to set initial values for the object’s attributes.

    Whenever you create an object using the new keyword, a constructor is invoked to allocate memory for the object and initialize it.

    Understanding constructors can greatly enhance your Java programming skills, particularly when working with large applications. Mastery of constructors is crucial for developing scalable, maintainable code, serving as a stepping stone toward more advanced programming concepts.

    Example of a Java Constructor

    Below is a simple demonstration of how constructors work in Java:

    // Java Program to demonstrate
    // Constructor usage
    public class MyClass {
    
        // Constructor
        MyClass() {
            System.out.println("Constructor called");
        }
    
        public static void main(String[] args) {
            // Creating an object which calls the constructor
            MyClass obj = new MyClass();
        }
    }

    Output:

    Constructor called

    Differences Between Constructors and Methods

    • Name: A constructor must have the same name as the class, whereas methods can have any name.
    • Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
    • Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.

    Syntax Example of a constructor:

    class MyClass {
        // A constructor
        MyClass() {
            // Initialization code here
        }
    }
    
    // Creating an object, which invokes the constructor
    MyClass obj = new MyClass();

    Output:

    Constructor called

    Differences Between Constructors and Methods

    • Name: A constructor must have the same name as the class, whereas methods can have any name.
    • Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
    • Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.

    Syntax example of a constructor:

    class MyClass {
        // A constructor
        MyClass() {
            // Initialization code here
        }
    }
    
    // Creating an object, which invokes the constructor
    MyClass obj = new MyClass();
    Why Do We Need Constructors?

    Constructors are used to initialize objects at the time of creation. For example, if we think of a class that represents a box, it might have fields like length, breadth, and height. When an object of this class is created, it wouldn’t make sense for it to have undefined dimensions, so constructors ensure these values are set.

    When Is a Constructor Called?

    A constructor is called whenever an object is instantiated using the new keyword. Some rules for writing constructors include:

    • The constructor must have the same name as the class.
    • A constructor cannot be abstractfinalstatic, or synchronized.
    • Access modifiers can be used with constructors to control which classes can instantiate objects.
    Types of Constructors in Java

    There are three types of constructors in Java:

    1. Default Constructor
    2. Parameterized Constructor
    3. Copy Constructor

    1. Default Constructor : A default constructor is one without parameters. If no constructor is defined explicitly, Java automatically provides a default constructor.

    Example:

    // Java Program to demonstrate Default Constructor
    public class MyClass {
    
        // Default Constructor
        MyClass() {
            System.out.println("Default constructor called");
        }
    
        public static void main(String[] args) {
            MyClass obj = new MyClass();
        }
    }

    Output:

    Default constructor called

    2. Parameterized Constructor : A parameterized constructor takes arguments, allowing you to set object properties with specific values at the time of creation.

    Example:

    // Java Program to demonstrate Parameterized Constructor
    public class MyClass {
        String name;
        int id;
    
        // Parameterized Constructor
        MyClass(String name, int id) {
            this.name = name;
            this.id = id;
        }
    
        public static void main(String[] args) {
            MyClass obj = new MyClass("John", 101);
            System.out.println("Name: " + obj.name + ", ID: " + obj.id);
        }
    }

    Output:

    Name: John, ID: 101

    3. Copy Constructor : A copy constructor is used to create a new object as a copy of an existing object. While Java does not provide a built-in copy constructor, you can create one manually by passing an object of the same class to the constructor.

    Example:

    // Java Program to demonstrate Copy Constructor
    public class MyClass {
        String name;
        int id;
    
        // Parameterized Constructor
        MyClass(String name, int id) {
            this.name = name;
            this.id = id;
        }
    
        // Copy Constructor
        MyClass(MyClass obj) {
            this.name = obj.name;
            this.id = obj.id;
        }
    
        public static void main(String[] args) {
            // Creating an object using a parameterized constructor
            MyClass obj1 = new MyClass("Alice", 202);
    
            // Creating a new object using the copy constructor
            MyClass obj2 = new MyClass(obj1);
    
            System.out.println("Name: " + obj2.name + ", ID: " + obj2.id);
        }
    }

    Output:

    Name: Alice, ID: 202
    Constructor Overloading

    Just like methods, constructors can also be overloaded by defining multiple constructors with different parameter lists. This allows for flexibility when creating objects with varying initial values.

    Example:

    // Java Program to demonstrate Constructor Overloading
    public class MyClass {
    
        // Constructor with one parameter
        MyClass(String name) {
            System.out.println("Name: " + name);
        }
    
        // Constructor with two parameters
        MyClass(String name, int age) {
            System.out.println("Name: " + name + ", Age: " + age);
        }
    
        public static void main(String[] args) {
            // Calling different constructors
            MyClass obj1 = new MyClass("Alice");
            MyClass obj2 = new MyClass("Bob", 25);
        }
    }

    Output:

    Name: Alice
    Name: Bob, Age: 25

    Inheritance in Java

    Java Inheritance Overview

    Inheritance is one of the core concepts in Object-Oriented Programming (OOP), allowing a class to inherit properties and behaviors (fields and methods) from another class. In Java, this mechanism enables the creation of new classes that reuse, extend, and modify the functionality of existing ones. A class that inherits from another class can access the fields and methods of the parent class, while also adding its own methods and fields.

    Why Do We Need Java Inheritance?

    Inheritance offers several benefits, such as promoting code reusabilitymethod overriding (which helps in achieving runtime polymorphism), and abstraction, allowing us to work with classes without needing to know the full details of their implementations. By leveraging inheritance, developers can create flexible and easily maintainable code.

    Key Benefits:
    • Code Reusability: Common code in the superclass can be reused in the child classes, reducing redundancy.
    • Method Overriding: Enables the child class to provide specific implementations for methods defined in the parent class, facilitating runtime polymorphism.
    • Abstraction: Hides complex details from the user and only shows relevant functionalities.
    Important Terms
    • Class: A blueprint for creating objects that share common attributes and behaviors.
    • Super Class (Parent Class): The class whose properties are inherited by another class.
    • Sub Class (Child Class): The class that inherits from another class. It can have additional fields and methods specific to itself.
    • Reusability: One of the major advantages of inheritance, where existing fields and methods from a parent class can be used in the child class without duplicating code.
    How to Implement Inheritance in Java?

    Inheritance in Java is implemented using the extends keyword. This keyword allows the subclass to extend the functionality of the superclass.

    Syntax:

    class SubClass extends SuperClass {
       // fields and methods
    }

    Example of Java Inheritance

    In this example, we have a base class Vehicle and a derived class Car that extends the Vehicle class. The TestDrive class is used to demonstrate the usage of inheritance.

    // Base class
    class Vehicle {
        public int speed;
        public int fuel;
    
        // Constructor
        public Vehicle(int speed, int fuel) {
            this.speed = speed;
            this.fuel = fuel;
        }
    
        // Methods
        public void accelerate(int increment) {
            speed += increment;
        }
    
        public void brake(int decrement) {
            speed -= decrement;
        }
    
        @Override
        public String toString() {
            return "Speed: " + speed + " km/h, Fuel: " + fuel + " L";
        }
    }
    
    // Derived class
    class Car extends Vehicle {
        public int numberOfDoors;
    
        // Constructor
        public Car(int speed, int fuel, int numberOfDoors) {
            super(speed, fuel); // Calling the constructor of the superclass
            this.numberOfDoors = numberOfDoors;
        }
    
        // Additional method for the Car class
        public void lockDoors() {
            System.out.println("Doors locked.");
        }
    
        @Override
        public String toString() {
            return super.toString() + ", Doors: " + numberOfDoors;
        }
    }
    
    // Driver class
    public class TestDrive {
        public static void main(String[] args) {
            Car myCar = new Car(80, 50, 4);
            System.out.println(myCar.toString());
        }
    }

    Output:

    Speed: 80 km/h, Fuel: 50 L, Doors: 4

    In this example, when an object of Car is created, the fields and methods from the Vehicle class are inherited, allowing the car to access properties like speed and fuel. The toString() method is overridden in the Car class to display additional information about the car.

    Types of Inheritance in Java

    Java supports several types of inheritance, each with its own structure and use cases:

    1. Single Inheritance: A subclass inherits from one superclass.
    2. Multilevel Inheritance: A chain of classes where a subclass inherits from a superclass, and another class inherits from that subclass.
    3. Hierarchical Inheritance: One superclass is inherited by multiple subclasses.
    4. Multiple Inheritance (Through Interfaces): In Java, a class can implement multiple interfaces, which allows it to inherit features from multiple sources. Java doesn’t support multiple inheritance with classes directly to avoid complexity, but interfaces provide a way to achieve this.
    5. Hybrid Inheritance: A combination of multiple and other forms of inheritance. Java supports this through interfaces and combinations of single and hierarchical inheritance.
    Example:

    In the following example, Dog is a subclass that inherits from the Animal superclass.

    // Superclass
    class Animal {
        public void sound() {
            System.out.println("This is an animal sound.");
        }
    }
    
    // Subclass
    class Dog extends Animal {
        public void bark() {
            System.out.println("The dog barks.");
        }
    }
    
    // Driver class
    public class TestAnimal {
        public static void main(String[] args) {
            Dog myDog = new Dog();
            myDog.sound();  // Inherited from Animal
            myDog.bark();   // Defined in Dog
        }
    }

    Output:

    This is an animal sound.
    The dog barks.

    Abstraction in Java

    Abstraction in Java refers to the practice of showing only the necessary details and functionalities to the user while hiding the underlying implementation. By doing so, unnecessary complexities are kept out of sight, making it easier to work with the code.

    Real-World Example of Abstraction:

    A remote control for a TV is a great example of abstraction. You press buttons to change channels or adjust the volume, without needing to understand the inner workings of the TV or how the remote communicates with it.

    What is Abstraction in Java?

    In Java, abstraction is implemented using abstract classes and interfaces. If you want complete (100%) abstraction, interfaces are the tool of choice.

    Data Abstraction is the process of focusing on the important characteristics of an object while ignoring irrelevant details. It helps in defining the key behaviors and properties of an object, distinguishing it from others in a meaningful way.

    Real-Life Example of Abstraction:

    Imagine driving a car. You know pressing the accelerator increases the speed, and pressing the brakes slows it down, but you don’t need to understand the detailed mechanism behind it. This is how abstraction simplifies interaction.

    Java Abstract Classes and Abstract Methods
    • An abstract class is defined with the abstract keyword.
    • An abstract method is declared without any implementation.
    • An abstract class can have both abstract methods and regular methods.
    • If a class contains abstract methods, it must be declared as abstract.
    • Abstract classes cannot be instantiated directly; they must be extended.
    • Abstract classes can have constructors, both parameterized and default.
    How to Implement Abstraction in Java

    1. Define an abstract class or interface to represent the abstraction.
    2. Declare common behaviors and properties as abstract methods within the class or interface.
    3. Extend the abstract class or implement the interface in concrete classes.
    4. Provide the specific implementations for the abstract methods in the concrete classes.
    5. Use these concrete classes to achieve the desired functionality in your program.

    When to Use Abstract Classes and Methods?

    Abstract classes are useful when you want to define a template that outlines the structure of an abstraction but leave some details to be implemented by subclasses. A common example is the Shape hierarchy in design or simulation software, where shapes like circles, squares, and triangles share some characteristics but differ in how they calculate their areas.

    Java Abstraction Example

    Example 1:

    // Abstract class for Shape
    abstract class Shape {
        String color;
    
        // Abstract methods
        abstract double area();
        public abstract String toString();
    
        // Constructor for Shape
        public Shape(String color) {
            this.color = color;
        }
    
        // Concrete method
        public String getColor() {
            return color;
        }
    }
    
    // Circle class extending Shape
    class Circle extends Shape {
        double radius;
    
        public Circle(String color, double radius) {
            super(color);
            this.radius = radius;
        }
    
        @Override
        double area() {
            return Math.PI * Math.pow(radius, 2);
        }
    
        @Override
        public String toString() {
            return "Circle color is " + getColor() + " and area is: " + area();
        }
    }
    
    // Rectangle class extending Shape
    class Rectangle extends Shape {
        double length, width;
    
        public Rectangle(String color, double length, double width) {
            super(color);
            this.length = length;
            this.width = width;
        }
    
        @Override
        double area() {
            return length * width;
        }
    
        @Override
        public String toString() {
            return "Rectangle color is " + getColor() + " and area is: " + area();
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            Shape circle = new Circle("Red", 5.0);
            Shape rectangle = new Rectangle("Blue", 4.0, 6.0);
    
            System.out.println(circle.toString());
            System.out.println(rectangle.toString());
        }
    }

    Output:

    Circle color is Red and area is: 78.53981633974483
    Rectangle color is Blue and area is: 24.0
    Using Interfaces for Abstraction

    Interfaces provide a way to achieve complete abstraction in Java. They contain method declarations but no method implementations.

    Example:

    // Shape interface
    interface Shape {
        double calculateArea();
    }
    
    // Circle class implementing Shape interface
    class Circle implements Shape {
        private double radius;
    
        public Circle(double radius) {
            this.radius = radius;
        }
    
        @Override
        public double calculateArea() {
            return Math.PI * radius * radius;
        }
    }
    
    // Rectangle class implementing Shape interface
    class Rectangle implements Shape {
        private double length, width;
    
        public Rectangle(double length, double width) {
            this.length = length;
            this.width = width;
        }
    
        @Override
        public double calculateArea() {
            return length * width;
        }
    }
    
    // Main class
    public class Main {
        public static void main(String[] args) {
            Shape circle = new Circle(5.0);
            Shape rectangle = new Rectangle(4.0, 6.0);
    
            System.out.println("Area of Circle: " + circle.calculateArea());
            System.out.println("Area of Rectangle: " + rectangle.calculateArea());
        }
    }

    Output:

    Area of Circle: 78.53981633974483
    Area of Rectangle: 24.0
    Advantages of Abstraction

    1. Simplifies Code: Reduces complexity by hiding implementation details.
    2. Increases Reusability: Encourages code reuse and avoids duplication.
    3. Enhances Security: Provides essential details while hiding the rest.
    4. Improves Maintainability: Easier to update and maintain.
    5. Supports Modularity: Breaks down complex systems into smaller parts.

    Disadvantages of Abstraction

    1. Increased Complexity: May add unnecessary layers if not used properly.
    2. Debugging Challenges: Can make debugging more difficult due to hidden layers.
    3. Potential Performance Overhead: Additional abstraction layers might affect performance.

    Encapsulation in Java

    Encapsulation is a core principle of object-oriented programming (OOP) in Java. It refers to the bundling of data (variables) and methods (functions) that operate on that data into a single unit, typically a class. Encapsulation ensures that the internal implementation of a class is hidden from outside access, only exposing a public interface that allows interaction with the class through methods. This technique enhances security and data integrity, as the internal state of an object can only be modified in controlled ways.

    In Java, encapsulation is achieved by declaring instance variables as private, which means they cannot be accessed directly from outside the class. To provide access to these variables, public getter and setter methods are used, which enable controlled access and modification of the private variables.

    Key Aspects of Encapsulation
    • Data hiding: By using private variables, you ensure that the internal details of a class are hidden.
    • Access through methods: Public getter and setter methods allow controlled access to the variables.
    • Modularity: It promotes better design by keeping data and methods together in a single unit.
    • Security and validation: Setters can include validation logic to prevent invalid data from being assigned to variables.

    Java Encapsulation Example

    // Employee Class demonstrating encapsulation
    class Employee {
        // Private fields, not accessible outside the class
        private String name;
        private int id;
    
        // Getter method for name
        public String getName() {
            return name;
        }
    
        // Setter method for name
        public void setName(String name) {
            this.name = name;
        }
    
        // Getter method for id
        public int getId() {
            return id;
        }
    
        // Setter method for id
        public void setId(int id) {
            this.id = id;
        }
    }
    
    // Main class to test encapsulation
    public class Main {
        public static void main(String[] args) {
            // Creating an object of the Employee class
            Employee emp = new Employee();
    
            // Using setter methods to assign values
            emp.setName("Alice");
            emp.setId(101);
    
            // Using getter methods to retrieve and display values
            System.out.println("Employee Name: " + emp.getName());
            System.out.println("Employee ID: " + emp.getId());
        }
    }

    Output:

    Employee Name: Alice
    Employee ID: 101
    Advantages of Encapsulation

    Data Hiding: Encapsulation prevents direct access to sensitive data, making it more secure.

    1. Improved Flexibility: You can make instance variables read-only or write-only by providing only a getter or setter method, depending on the requirement.
    2. Reusability: Encapsulated code is easier to maintain, reuse, and extend because it minimizes interdependencies.
    3. Easy Testing: Since encapsulated code separates data and functions, it is easier to test individual components.
    4. Adaptability: The internal implementation can change without affecting code that interacts with the class.

    Disadvantages of Encapsulation
    • Increased Complexity: Encapsulation may lead to more boilerplate code, such as writing getter and setter methods for each private variable.
    • Difficult to Understand: Excessive use of encapsulation can make code more difficult to follow, especially if it adds layers of abstraction.
    • Limited Flexibility: Over-encapsulation can sometimes restrict the flexibility of your design, making it harder to quickly implement new features.
    Java Encapsulation Example (Area Calculation)

    Here’s another example that demonstrates encapsulation in a different context, where it is used to calculate the area of a rectangle:

    // Rectangle Class to calculate area using encapsulation
    class Rectangle {
        // Private fields for length and breadth
        private int length;
        private int breadth;
    
        // Constructor to initialize length and breadth
        public Rectangle(int length, int breadth) {
            this.length = length;
            this.breadth = breadth;
        }
    
        // Getter for length
        public int getLength() {
            return length;
        }
    
        // Setter for length
        public void setLength(int length) {
            this.length = length;
        }
    
        // Getter for breadth
        public int getBreadth() {
            return breadth;
        }
    
        // Setter for breadth
        public void setBreadth(int breadth) {
            this.breadth = breadth;
        }
    
        // Method to calculate and print area
        public void calculateArea() {
            int area = length * breadth;
            System.out.println("Area of the Rectangle: " + area);
        }
    }
    
    // Main class to test encapsulation in rectangle class
    public class Main {
        public static void main(String[] args) {
            // Creating a rectangle object
            Rectangle rect = new Rectangle(5, 10);
    
            // Accessing and modifying private fields via getters and setters
            rect.setLength(8);
            rect.setBreadth(12);
    
            // Calculating and printing the area
            rect.calculateArea();
        }
    }

    Output:

    Area of the Rectangle: 96

    What are Interfaces in Java?

    In Java, an interface is an abstract type used to specify a set of behaviors that a class must implement. It provides a blueprint for a class, defining static constants and abstract methods (methods without a body). An interface is primarily used to achieve abstraction and multiple inheritance. Traditionally, interfaces in Java could only contain abstract methods and final variables, but with the introduction of Java 8, interfaces can also contain default and static methods. Interfaces help define a “contract” or “is-a” relationship between classes, specifying the methods they must implement, without worrying about the internal details.

    Syntax for Java Interfaces

    To declare an interface in Java, use the interface keyword. By default, all methods declared in an interface are abstract and public, and all fields are public, static, and final.

    interface InterfaceName {
        // Declare constant fields
        // Declare abstract methods (no implementation)
    }

    A class that implements an interface must provide implementations for all the methods declared in the interface, using the implements keyword.

    Features of Interfaces

    1. Achieves total abstraction: An interface allows complete abstraction since it contains no method implementation.
    2. Multiple inheritance: While Java does not support multiple inheritance with classes, an interface allows classes to implement multiple interfaces.
    3. Loose coupling: Interfaces enable loose coupling between classes as they provide a level of abstraction.
    4. Final variables: All variables declared in an interface are final, static, and public by default.

    Relationship Between Class and Interface

    In Java, a class can implement one or more interfaces, but an interface can only be implemented by a class, not the other way around. Classes must implement all the methods defined by the interfaces they implement. Additionally, interfaces can extend other interfaces, allowing for a flexible hierarchy of behavior definitions.

    Example of a Simple Java Interface

    interface Player {
        int id = 10; // Constant by default
        void move(); // Abstract method
    }
    class TestClass implements Player {
        public void move() {
            System.out.println("Player moves");
        }
    
        public static void main(String[] args) {
            TestClass player = new TestClass();
            player.move();
            System.out.println("Player ID: " + Player.id);
        }
    }

    Output:

    Player moves
    Player ID: 10

    Example: Vehicle Interface

    Consider the example of vehicles like bicycles and cars that have common functionalities. We can create a Vehicle interface to define the general behaviors that every vehicle must implement, and let each vehicle type provide its specific implementation.

    interface Vehicle {
        void changeGear(int gear);
        void speedUp(int increment);
        void applyBrakes(int decrement);
    }
    
    class Bicycle implements Vehicle {
        int speed;
        int gear;
    
        public void changeGear(int newGear) {
            gear = newGear;
        }
    
        public void speedUp(int increment) {
            speed += increment;
        }
    
        public void applyBrakes(int decrement) {
            speed -= decrement;
        }
    
        public void display() {
            System.out.println("Speed: " + speed + " Gear: " + gear);
        }
    }
    
    class Car implements Vehicle {
        int speed;
        int gear;
    
        public void changeGear(int newGear) {
            gear = newGear;
        }
    
        public void speedUp(int increment) {
            speed += increment;
        }
    
        public void applyBrakes(int decrement) {
            speed -= decrement;
        }
    
        public void display() {
            System.out.println("Speed: " + speed + " Gear: " + gear);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Bicycle bike = new Bicycle();
            bike.changeGear(2);
            bike.speedUp(10);
            bike.applyBrakes(3);
            System.out.println("Bicycle state:");
            bike.display();
    
            Car car = new Car();
            car.changeGear(3);
            car.speedUp(20);
            car.applyBrakes(5);
            System.out.println("Car state:");
            car.display();
        }
    }

    Output:

    Bicycle state:
    Speed: 7 Gear: 2
    Car state:
    Speed: 15 Gear: 3
    Multiple Inheritance Using Interfaces

    Java doesn’t allow multiple inheritance with classes, but interfaces provide a way to achieve it. A class can implement multiple interfaces, inheriting behaviors from each one.

    interface Machine {
        void start();
    }
    
    interface Sound {
        void playSound();
    }
    
    class Robot implements Machine, Sound {
        public void start() {
            System.out.println("Robot started.");
        }
    
        public void playSound() {
            System.out.println("Robot playing sound.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Robot r = new Robot();
            r.start();
            r.playSound();
        }
    }

    Output:

    Robot started.
    Robot playing sound.

    ‘this’ reference in Java

    In Java, this is a reference variable that refers to the current object instance. It helps in distinguishing between instance variables and parameters with the same name and can be used for various purposes such as invoking class methods, passing the current object as a parameter, and improving code readability.

    Java this Reference Example:

    In Java, this refers to the object on which a method or constructor is being invoked. It allows access to the instance variables and methods of the current object.

    Here’s an example demonstrating the usage of this reference:

    // Java program demonstrating the use of 'this' reference
    
    class Employee {
        String name;
        int id;
    
        // Constructor
        Employee(String name, int id) {
            this.name = name;  // 'this' refers to the instance variable 'name'
            this.id = id;      // 'this' refers to the instance variable 'id'
        }
    
        // Method to display employee details
        public void displayDetails() {
            System.out.println("Employee Name: " + this.name);
            System.out.println("Employee ID: " + this.id);
        }
    
        // Main method
        public static void main(String[] args) {
            Employee emp1 = new Employee("John", 101);
            Employee emp2 = new Employee("Alice", 102);
    
            emp1.displayDetails();
            emp2.displayDetails();
        }
    }

    Output:

    Employee Name: John
    Employee ID: 101
    
    Employee Name: Alice
    Employee ID: 102

    Explanation:

    In the above example, this is used in the constructor to refer to the instance variables name and id, differentiating them from the constructor’s parameters. The displayDetails method prints out the details of each employee using the this reference.

    Different Uses of this in Java:

    1. Referring to the Current Class Instance Variables :You can use this to differentiate between instance variables and parameters with the same name.

    class Test {
        int x;
        Test(int x) {
            this.x = x;  // 'this' is used to differentiate the instance variable from the constructor parameter
        }
    }

    2. Invoking the Current Class Constructor :this() can be used to call another constructor in the same class.

    class Test {
        int x;
        Test() {
            this(10);  // Calls the parameterized constructor
        }
        Test(int x) {
            this.x = x;
        }
    }

    3. Returning the Current Class Instance: this can be used to return the current instance of the class.

    class Test {
        int x;
        Test(int x) {
            this.x = x;
        }
        Test getObject() {
            return this;  // Returns the current object
        }
    }

    4. Passing the Current Class Instance as a Method Parameter: this can be passed as an argument to methods or constructors.

    class Test {
        void display(Test obj) {
            System.out.println("Object received: " + obj);
        }
        void sendThis() {
            display(this);  // Passes the current object as a parameter
        }
    }

    5. Invoking the Current Class Method:this can be used to call a method from within another method.

    class Test {
        void display() {
            this.show();  // Calls the show method
        }
        void show() {
            System.out.println("Inside show method");
        }
    }
  • Arrays

    Arrays in Java

    Operators are the foundation of any programming language, enabling us to perform various computations, logic evaluations, and functions. Java offers several types of operators that are categorized based on the functions they perform.

    Array Initialization in C

    Initialization in C involves assigning initial values to variables. When an array is declared or memory is allocated for it, its elements initially contain garbage values. To give these elements meaningful values, we initialize the array. There are several methods for initializing an array in C.

    1. Array Initialization During Declaration :This method involves initializing the array at the time of its declaration. We use an initializer list, which is a set of values enclosed in curly braces { } and separated by commas, to assign values to the array.

    Examples:

    int[] numbers; // Declaring an integer array

    This declares an integer array numbers. However, it does not allocate memory yet.

    2. Creating an Array :Allocate memory using the new keyword:

    numbers = new int[5]; // Creates an array of 5 integers

    This initializes the array to hold 5 integers, with default values of 0.

    3. Accessing Array Elements :You can access and modify array elements using their index (starting from 0):

    numbers[0] = 10; // Setting the first element to 10
    int firstElement = numbers[0]; // Accessing the first element

    4. Changing Array Elements :To update an element, assign a new value:

    numbers[0] = 20; // Changes the first element to 20

    5. Array Length :You can get the length of an array using its length property:

    int length = numbers.length; // Returns 5 for this array

    6. Array Initialization :You can declare and initialize arrays in different ways:

    int[] numbers = new int[5]; // Declaration + memory allocation
    int[] otherNumbers = {1, 2, 3, 4, 5}; // Array literal initialization

    7. Iterating Through Arrays :You can loop through arrays using a for loop or for-each loop:

    for (int i = 0; i < numbers.length; i++) {
        System.out.println(numbers[i]);
    }
    
    for (int number : numbers) {
        System.out.println(number); // For-each loop
    }

    8. Multidimensional Arrays :Java supports multidimensional arrays, like 2D arrays (matrix):

    int[][] matrix = new int[3][3]; // 2D array
    matrix[0][0] = 1;

    Example:

    int[][] multiDimArray = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    }; // A 3x3 matrix

    9. Array of Objects ;Arrays can also hold objects of a class:

    Student[] students = new Student[3]; // Array of Student objects
    students[0] = new Student("John", 1);

    Output:

    num1 = 20
    num2 = 10
    The product = 200

    10. Passing Arrays to Methods :You can pass arrays to methods:

    public class MultiplicationExample {
        public static void main(String[] args) {
            int num1 = 20, num2 = 10;
            int product = num1 * num2;
            System.out.println("The product = " + product);
        }
    }

    Calling the method:

    printArray(numbers);

    11. Returning Arrays from Methods :A method can return an array:

    public static int[] getArray() {
        return new int[] {1, 2, 3};
    }

    12. Cloning Arrays :To clone an array (create a shallow copy):

    int[] cloneArray = numbers.clone();

    For multidimensional arrays, the cloning creates a shallow copy, meaning only the references are copied, not the actual elements.

    13. Handling Array Exceptions :If you try to access an element outside the array size, Java throws an ArrayIndexOutOfBoundsException:

    System.out.println(numbers[5]); // Throws an exception if the array has fewer elements

    Arrays class in Java

    The Arrays class in Java, part of the java.util package, provides several static methods to help developers perform operations on arrays in a more optimized and efficient manner. Here are some of the most common methods:

    Key Methods in Arrays Class:

    1. asList(): Converts an array to a fixed-size list.

    Integer[] arr = {1, 2, 3};
    List<Integer> list = Arrays.asList(arr);

    2. binarySearch(): Searches for a specific element using the binary search algorithm.

    int[] arr = {1, 2, 3, 4, 5};
    int index = Arrays.binarySearch(arr, 3); // Returns index of 3

    3. compare(): Compares two arrays lexicographically.

    int result = Arrays.compare(arr1, arr2);

    4. copyOf(): Copies the array to a new array of a specified length.

    int[] newArr = Arrays.copyOf(arr, 10); // Extends or truncates

    5. copyOfRange(): Copies a range of an array.

    int[] newArr = Arrays.copyOfRange(arr, 1, 3);

    6. deepEquals():Compares two arrays deeply for nested arrays.

    boolean isEqual = Arrays.deepEquals(arr1, arr2);

    7. deepHashCode(): Returns the hash code based on the “deep contents” of an array.

    int hashCode = Arrays.deepHashCode(arr);

    8. deepToString(): Converts an array to a string representation, handling nested arrays.

    String arrStr = Arrays.deepToString(arr);

    9. equals(): Compares two arrays for equality.

    boolean isEqual = Arrays.equals(arr1, arr2);

    10. fill(): Fills an array with a specified value.

    Arrays.fill(arr, 100); // Fills all elements with 100

    11. hashCode(): Returns the hash code for an array.

    int hash = Arrays.hashCode(arr);

    12. mismatch(): Finds the first index where two arrays differ.

    int mismatchIndex = Arrays.mismatch(arr1, arr2);

    13. parallelSort(): Sorts the array in parallel for faster sorting.

    Arrays.parallelSort(arr);

    14. sort(): Sorts an array in ascending order.

    Arrays.sort(arr);

    15. spliterator(): Returns a Spliterator covering the array.

    Spliterator<Integer> spliterator = Arrays.spliterator(arr);

    16. stream(): Converts an array into a sequential stream.

    Stream<int[]> stream = Arrays.stream(arr);

    17. toString(): Converts an array to a string representation.

    String arrStr = Arrays.toString(arr);
    Multidimensional Arrays

    Multidimensional arrays, simply put, are arrays made up of arrays. Data within these arrays are stored in a tabular structure, typically in row-major order.

    Syntax:

    data_type[1st_dimension][2nd_dimension][]...[Nth_dimension] array_name = new data_type[size1][size2]...[sizeN];

    Where:

    • data_type: The type of data stored in the array (e.g., intchar, etc.).
    • dimension: Specifies the dimension of the array (e.g., 2D, 3D).
    • array_name: The name assigned to the array.
    • size1, size2,…, sizeN: Dimensions defining the size of the array.

    Examples:

    Two-dimensional array:

    int[][] twoD_arr = new int[10][20];

    The total number of elements a multidimensional array can store is the product of its dimensions. For example, an array int[][] x = new int[10][20] can hold a total of 10×20=20010 \times 20 = 20010×20=200 elements. Similarly, int[][][] x = new int[5][10][20] can store 5×10×20=10005 \times 10 \times 20 = 10005×10×20=1000 elements.

    Applications of Multidimensional Arrays
    • Tabular Data: These arrays are often used to store tabular data, such as a student’s roll number and marks. A more complex use is to store images in 3D arrays.
    • Dynamic Programming: Many dynamic programming problems leverage multidimensional arrays to store intermediate states.
    • Algorithms: Common applications include matrix multiplication, adjacency matrices in graphs, and grid-based search problems.
    Two-Dimensional Array (2D Array)

    A 2D array is the most basic form of a multidimensional array and can be viewed as an array of one-dimensional arrays.

    Declaration (Indirect Method):

    Example:

    int[][] arr = new int[10][20];

    Initialization:

    arr[0][0] = 1;

    Examples:

    public class TwoDArrayExample {
        public static void main(String[] args) {
            int[][] arr = new int[10][20];
            arr[0][0] = 1;
    
            System.out.println("arr[0][0] = " + arr[0][0]);
        }
    }

    Output:

    arr[0][0] = 1

    Example: 2D array with default values in a 4×4 matrix:

    public class TwoDArray {
        public static void main(String[] args) {
            int rows = 4;
            int columns = 4;
            int[][] array = new int[rows][columns];
    
            int value = 1;
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    array[i][j] = value++;
                }
            }
    
            System.out.println("The 2D array is:");
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    System.out.print(array[i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    Output:

    The 2D array is:
    1 2 3 4
    5 6 7 8
    9 10 11 12
    13 14 15 16

    Declaration (Direct Method):

    int[][] arr = {{1, 2}, {3, 4}};

    Output:

    After adding: 8

    Example:

    public class DirectTwoDArray {
        public static void main(String[] args) {
            int[][] arr = { { 1, 2 }, { 3, 4 } };
    
            for (int i = 0; i < 2; i++) {
                for (int j = 0; j < 2; j++) {
                    System.out.println("arr[" + i + "][" + j + "] = " + arr[i][j]);
                }
            }
        }
    }

    Output:

    arr[0][0] = 1
    arr[0][1] = 2
    arr[1][0] = 3
    arr[1][1] = 4

    Accessing Elements:

    To access elements in a 2D array, the syntax is:

    array[row_index][column_index];

    For example:

    arr[0][0] = 1;
    Three-Dimensional Array (3D Array)

    A 3D array can be seen as an array of 2D arrays.

    Declaration (Indirect Method):

    int[][][] arr = new int[10][20][30];

    Initialization:

    arr[0][0][0] = 1;

    Different Ways To Declare And Initialize 2-D Array

    A multi-dimensional array is an array with more than one dimension, commonly used as 2D or 3D arrays. Essentially, a multi-dimensional array is an array of arrays. A typical example of a 2D array is a chessboard, which consists of a grid of 64 square boxes arranged in 8 rows and 8 columns. Similarly, a 2D array can be visualized as a grid, where each element is identified by a row and column number. Accessing elements in a 2D array is similar to working with an Excel sheet, using row and column indices.

    Two-dimensional arrays (2D arrays) are useful for implementing structures like games (Tic-Tac-Toe, Chess) or storing image pixels.

    Declaring a 2D Array in Java

    A 2D array can be declared in two common ways:

    data_type array_name[][];   // OR
    data_type[][] array_name;
    • data_type: This defines the type of elements the array will store, such as intStringboolean, etc. Java, being statically typed, requires the data type to be declared.
    • array_name: This is the name you assign to the array.

    Example: Declaring Different Types of 2D Arrays

    data_type[][] array_Name = new data_type[no_of_rows][no_of_columns];

    Here, no_of_rows and no_of_columns define the number of rows and columns in the array. The total number of elements in a 2D array is calculated by multiplying the number of rows by the number of columns. For instance:

    int[][] myArray = new int[4][5];  // A 2D array with 4 rows and 5 columns

    By default, Java assigns default values to the array elements depending on the data type. Below are various approaches to initialize a 2D array.

    Different Approaches for Initializing 2D Arrays

    Approach 1: Declaration and Initialization

    In this approach, we declare the array and immediately initialize it.

    int[][][] arr = { {{1, 2}, {3, 4}}, {{5, 6}, {7, 8}} };

    Example:

    public class Example {
        public static void main(String[] args) {
            int[][] integer2DArray = new int[3][4];
            System.out.println("Default value of int array element: " + integer2DArray[0][0]);  // Outputs 0
    
            String[][] string2DArray = new String[2][3];
            System.out.println("Default value of String array element: " + string2DArray[0][0]);  // Outputs null
    
            boolean[][] boolean2DArray = new boolean[2][2];
            System.out.println("Default value of boolean array element: " + boolean2DArray[0][0]);  // Outputs false
        }
    }

    Approach 2: Initialization with Values

    Here, we initialize the array elements directly without specifying the number of rows and columns. Java automatically deduces the size.

    Example:

    public class Example {
        public static void main(String[] args) {
            String[][] courses = {
                { "Math", "Science", "History" },       // Row 1
                { "Biology", "Physics", "Chemistry" },  // Row 2
                { "English", "Art" }                    // Row 3
            };
    
            System.out.println("Course in first row: " + courses[0][0]);  // Math
            System.out.println("Course in second row: " + courses[1][2]);  // Chemistry
            System.out.println("Course in third row: " + courses[2][1]);  // Art
        }
    }

    Approach 3: Initializing Each Element Separately

    This method allows you to initialize elements individually.

    Example:

    public class Example {
        public static void main(String[] args) {
            int[][] marks = new int[2][2];
            marks[0][0] = 85;
            marks[0][1] = 90;
            marks[1][0] = 78;
            marks[1][1] = 88;
    
            System.out.println("marks[0][0] = " + marks[0][0]);
            System.out.println("marks[1][1] = " + marks[1][1]);
        }
    }

    Approach 4: Using Loops for Initialization

    When dealing with large arrays, initializing elements manually can be cumbersome. A more efficient approach is using loops.

    Example:

    public class Example {
        public static void main(String[] args) {
            int rows = 4, columns = 3;
            int[][] array = new int[rows][columns];
    
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    array[i][j] = i + j;
                }
            }
    
            // Printing array elements
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < columns; j++) {
                    System.out.print(array[i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    Approach 5: Jagged Arrays (Different Number of Columns per Row)

    A jagged array allows rows to have different numbers of columns.

    Example:

    public class Example {
        public static void main(String[] args) {
            int[][] jaggedArray = new int[2][];
            jaggedArray[0] = new int[3];  // First row has 3 columns
            jaggedArray[1] = new int[2];  // Second row has 2 columns
    
            jaggedArray[0][0] = 1;
            jaggedArray[0][1] = 2;
            jaggedArray[0][2] = 3;
            jaggedArray[1][0] = 4;
            jaggedArray[1][1] = 5;
    
            // Printing the jagged array
            for (int i = 0; i < jaggedArray.length; i++) {
                for (int j = 0; j < jaggedArray[i].length; j++) {
                    System.out.print(jaggedArray[i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    Jagged Arrays in Java

    A jagged array is a type of multidimensional array where the number of columns varies from row to row. This makes the jagged array more flexible compared to a rectangular 2D array. Here’s a simplified representation of a jagged array in memory:

    Row 1: [ ]
    Row 2: [   ]
    Row 3: [         ]
    ...

    Declaring and Initializing a Jagged Array

    Syntax:

    data_type array_name[][] = new data_type[n][];
    array_name[0] = new data_type[n1];  // n1 = no. of columns in row-1
    array_name[1] = new data_type[n2];  // n2 = no. of columns in row-2
    ...

    Alternative Ways to Initialize a Jagged Array:

    int[][] arr = new int[][] {
        new int[] {10, 20, 30, 40},
        new int[] {50, 60, 70, 80, 90, 100},
        new int[] {110, 120}
    };

    or simply:

    int[][] arr = {
        {10, 20, 30, 40},
        {50, 60, 70, 80, 90, 100},
        {110, 120}
    };

    Example 1: Jagged Array with Uneven Rows

    public class Main {
        public static void main(String[] args) {
            // Declare a 2D array with 2 rows
            int[][] jaggedArray = new int[2][];
    
            // First row has 3 columns
            jaggedArray[0] = new int[3];
    
            // Second row has 2 columns
            jaggedArray[1] = new int[2];
    
            // Initializing the array
            int value = 0;
            for (int i = 0; i < jaggedArray.length; i++) {
                for (int j = 0; j < jaggedArray[i].length; j++) {
                    jaggedArray[i][j] = value++;
                }
            }
    
            // Displaying the values of the jagged array
            System.out.println("Contents of the Jagged Array:");
            for (int i = 0; i < jaggedArray.length; i++) {
                for (int j = 0; j < jaggedArray[i].length; j++) {
                    System.out.print(jaggedArray[i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    Output:

    Contents of the Jagged Array:
    0 1 2
    3 4

    Example 2: Creating a Jagged Array with Incremental Row Sizes

    In this example, the first row contains one element, the second row contains two elements, and so on.

    public class Main {
        public static void main(String[] args) {
            int rows = 5;
    
            // Declaring a jagged array with 5 rows
            int[][] jaggedArray = new int[rows][];
    
            // Creating the jagged array with varying columns per row
            for (int i = 0; i < jaggedArray.length; i++) {
                jaggedArray[i] = new int[i + 1];  // ith row has i+1 columns
            }
    
            // Initializing the array
            int value = 0;
            for (int i = 0; i < jaggedArray.length; i++) {
                for (int j = 0; j < jaggedArray[i].length; j++) {
                    jaggedArray[i][j] = value++;
                }
            }
    
            // Displaying the values of the jagged array
            System.out.println("Contents of the Jagged Array:");
            for (int i = 0; i < jaggedArray.length; i++) {
                for (int j = 0; j < jaggedArray[i].length; j++) {
                    System.out.print(jaggedArray[i][j] + " ");
                }
                System.out.println();
            }
        }
    }

    Output:

    Contents of the Jagged Array:
    0
    1 2
    3 4 5
    6 7 8 9
    10 11 12 13 14

    Final Arrays

    In Java, the final keyword prevents reassigning a reference to another object, but it doesn’t make the referenced object itself immutable. This is applicable to arrays as well. Once an array is declared as final, the reference to that array cannot be changed, but the elements within the array can be modified. This means we can change the state of the object (the contents of the array), but not the reference to the object itself.

    Illustration:

    // Java Program Demonstrating Final Arrays
    
    public class FinalArrayDemo {
        public static void main(String[] args) {
            final int[] arr = { 1, 2, 3, 4, 5 };
    
            // Modifying an element in the final array
            arr[3] = 10;
    
            // Displaying the updated array
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    }

    Output:

    1
    2
    3
    10
    5

    Implementation: Here’s another example showing how you can manipulate the contents of a final array:

    // Java Program Demonstrating Final Arrays
    
    public class FinalArrayExample {
        public static void main(String[] args) {
            final int[] arr = { 2, 4, 6, 8, 10 };
    
            // Modifying array elements
            for (int i = 0; i < arr.length; i++) {
                arr[i] = arr[i] * 2;
                System.out.println(arr[i]);
            }
        }
    }

    Output:

    4
    8
    12
    16
    20

    Explanation:

    Even though the array arr is declared as final, you can still change its individual elements. The final keyword simply means that the reference to the array cannot be reassigned to another array. This behavior is consistent with how object references work in Java: when you declare an object as final, the reference can’t be changed, but the object’s internal state can still be modified.

    Example 1:

    // Program demonstrating modification of object fields
    
    class Test {
        int value = 100;
    
        public static void main(String[] args) {
            final Test obj = new Test();
            obj.value = 200;  // Modifying the internal state
            System.out.println(obj.value);
        }
    }

    Output:

    200

    util.Arrays vs reflect.Array in Java with Examples

    The Array class in the java.lang.reflect package is part of Java Reflection and provides static methods to dynamically create and access Java arrays. This class is final, meaning it cannot be instantiated or extended. All its methods are static and accessed via the class name itself. On the other hand, the Arrays class in the java.util package is part of the Java Collection Framework and contains various utility methods for manipulating arrays, such as sorting and searching.

    While both classes deal with arrays, the key difference is their usage. The Array class ensures type safety and is mainly used for reflective access to arrays. The Arrays class, on the other hand, offers a variety of methods for array operations.

    Here is a breakdown of the differences between Array and Arrays:

    FactorArrayArrays
    PackageBelongs to java.lang.reflectBelongs to java.util
    Class Hierarchyjava.lang.Object → java.lang.reflect.Arrayjava.lang.Object → java.util.Arrays
    ImmutabilityImmutable and finalNot immutable
    Declarationpublic final class Array extends Objectpublic class Arrays extends Object
    UsageProvides methods to create and access arrays reflectively, ensuring type safetyContains utility methods for array manipulation (sorting, searching, etc.)

    Example: Illustrating the Usage of Array vs Arrays

    import java.lang.reflect.Array;
    import java.util.Arrays;
    
    public class ArrayVsArraysExample {
    
        public static void main(String[] args) {
    
            // Creating an integer array of size 5
            int[] intArray = new int[5];
    
            // Adding an element to the array using Array class
            Array.setInt(intArray, 0, 42);
    
            // Printing the array using Arrays class
            System.out.println(Arrays.toString(intArray));
        }
    }

    Output:

    [42, 0, 0, 0, 0]

  • Operators in Java

    Operators

    Operators are the foundation of any programming language, enabling us to perform various computations, logic evaluations, and functions. Java offers several types of operators that are categorized based on the functions they perform. These include:

    • Arithmetic Operators
    • Unary Operators
    • Assignment Operator
    • Relational Operators
    • Logical Operators
    • Ternary Operator
    • Bitwise Operators
    • Shift Operators

    This section focuses on Arithmetic Operators, which allow us to carry out mathematical operations on basic data types. These operators can either be unary (applied to one operand) or binary (applied to two operands). Let’s dive into each arithmetic operator in Java.

    Arithmetic Operators in Java

    1. Addition (+)

    This binary operator is used to add two operands.

    Syntax:

    num1 + num2

    Example:

    int num1 = 10, num2 = 20;
    int sum = num1 + num2;

    Output:

    num1 = 10
    num2 = 20
    The sum = 30

    Java Code Example:

    public class AdditionExample {
        public static void main(String[] args) {
            int num1 = 10, num2 = 20;
            int sum = num1 + num2;
            System.out.println("The sum = " + sum);
        }
    }

    2. Subtraction (-)

    This binary operator is used to subtract the second operand from the first operand.

    Syntax:

    num1 - num2

    Example:

    int num1 = 20, num2 = 10;
    int difference = num1 - num2;

    Output:

    num1 = 20
    num2 = 10
    The difference = 10

    Java Code Example:

    public class SubtractionExample {
        public static void main(String[] args) {
            int num1 = 20, num2 = 10;
            int difference = num1 - num2;
            System.out.println("The difference = " + difference);
        }
    }

    3. Multiplication (*)

    This binary operator multiplies two operands.

    Syntax:

    num1 * num2

    Example:

    int num1 = 20, num2 = 10;
    int product = num1 * num2;

    Output:

    num1 = 20
    num2 = 10
    The product = 200

    Java Code Example:

    public class MultiplicationExample {
        public static void main(String[] args) {
            int num1 = 20, num2 = 10;
            int product = num1 * num2;
            System.out.println("The product = " + product);
        }
    }

    4. Division (/)

    This binary operator divides the first operand (dividend) by the second operand (divisor) and returns the quotient.

    Syntax:

    num1 / num2

    Example:

    int num1 = 20, num2 = 10;
    int quotient = num1 / num2;

    Output:

    num1 = 20
    num2 = 10
    The quotient = 2

    Java Code Example:

    public class DivisionExample {
        public static void main(String[] args) {
            int num1 = 20, num2 = 10;
            int quotient = num1 / num2;
            System.out.println("The quotient = " + quotient);
        }
    }

    5. Modulus (%)

    This binary operator returns the remainder when the first operand (dividend) is divided by the second operand (divisor).

    Syntax:

    num1 % num2

    Example:

    int num1 = 5, num2 = 2;
    int remainder = num1 % num2;

    Output:

    num1 = 5
    num2 = 2
    The remainder = 1

    Java Code Example:

    public class ModulusExample {
        public static void main(String[] args) {
            int num1 = 5, num2 = 2;
            int remainder = num1 % num2;
            System.out.println("The remainder = " + remainder);
        }
    }

    Example Program Implementing All Arithmetic Operators

    The following Java program takes user input and performs addition, subtraction, multiplication, and division:

    import java.util.Scanner;
    
    public class ArithmeticOperators {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
    
            System.out.print("Enter the first number: ");
            double num1 = sc.nextDouble();
    
            System.out.print("Enter the second number: ");
            double num2 = sc.nextDouble();
    
            double sum = num1 + num2;
            double difference = num1 - num2;
            double product = num1 * num2;
            double quotient = num1 / num2;
    
            System.out.println("The sum of the two numbers is: " + sum);
            System.out.println("The difference of the two numbers is: " + difference);
            System.out.println("The product of the two numbers is: " + product);
            System.out.println("The quotient of the two numbers is: " + quotient);
        }
    }

    Input:

    Enter the first number: 20
    Enter the second number: 10

    Output:

    The sum of the two numbers is: 30.0
    The difference of the two numbers is: 10.0
    The product of the two numbers is: 200.0
    The quotient of the two numbers is: 2.0

    Java Unary Operator

    Unary operators in Java operate on a single operand. These operators are used for tasks such as incrementing, decrementing, and negating values. Let’s explore the various unary operators and how they function.

    Operator 1: Unary Minus (-)

    This operator is used to reverse the sign of a value, converting a positive value into a negative one, or vice versa.

    Syntax:

    -(operand)

    Example:

    // Java Program demonstrating Unary Minus
    class UnaryMinus {
        public static void main(String[] args) {
            int num = 20;
            System.out.println("Number = " + num);
    
            // Applying unary minus
            num = -num;
            System.out.println("After applying unary minus = " + num);
        }
    }

    Output:

    Number = 20
    After applying unary minus = -20

    Operator 2: NOT Operator (!)

    This operator negates a boolean expression, flipping true to false and vice versa.

    Syntax:

    !(operand)

    Example:

    // Java Program demonstrating NOT Operator
    class NotOperator {
        public static void main(String[] args) {
            boolean flag = true;
            int a = 10, b = 1;
    
            System.out.println("Initial flag: " + flag);
            System.out.println("a = " + a + ", b = " + b);
    
            // Applying NOT operator
            System.out.println("Negated flag: " + !flag);
            System.out.println("!(a < b) = " + !(a < b));
            System.out.println("!(a > b) = " + !(a > b));
        }
    }

    Output:

    Initial flag: true
    a = 10, b = 1
    Negated flag: false
    !(a < b) = true
    !(a > b) = false

    Operator 3: Increment (++)

    The increment operator increases the value of an operand by one. It can be used in two forms:

    • Post-Increment: The operand is incremented after its current value is used.
    • Pre-Increment: The operand is incremented before its current value is used.

    Example:

    int num = 5;
    System.out.println("Post-increment: " + (num++)); // Uses current value, then increments
    System.out.println("Pre-increment: " + (++num));  // Increments, then uses new value

    Operator 4: Decrement (--)

    Similar to the increment operator, the decrement operator decreases the operand’s value by one and can also be used in two forms:

    • Post-Decrement: Decrements the operand after its current value is used.
    • Pre-Decrement: Decrements the operand before its current value is used.

    Example:

    int num = 5;
    System.out.println("Post-decrement: " + (num--)); // Uses current value, then decrements
    System.out.println("Pre-decrement: " + (--num));  // Decrements, then uses new value

    Operator 5: Bitwise Complement (~)

    This operator inverts all the bits of the operand. It converts 0s to 1s and 1s to 0s.

    Syntax:

    ~(operand)

    Example:

    // Java Program demonstrating Bitwise Complement Operator
    class BitwiseComplement {
        public static void main(String[] args) {
            int num = 5;
            System.out.println("Number: " + num);
    
            // Applying bitwise complement
            System.out.println("Bitwise complement: " + ~num);
        }
    }

    Output:

    Number: 5
    Bitwise complement: -6

    Example Program Demonstrating All Unary Operators

    Here’s a comprehensive program that demonstrates the use of all basic unary operators with user input:

    import java.util.Scanner;
    
    public class UnaryOperatorsExample {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
    
            System.out.print("Enter a number: ");
            int num = sc.nextInt();
    
            // Unary plus
            int result = +num;
            System.out.println("Unary plus result: " + result);
    
            // Unary minus
            result = -num;
            System.out.println("Unary minus result: " + result);
    
            // Pre-increment
            result = ++num;
            System.out.println("Pre-increment result: " + result);
    
            // Post-increment
            result = num++;
            System.out.println("Post-increment result: " + result);
    
            // Pre-decrement
            result = --num;
            System.out.println("Pre-decrement result: " + result);
    
            // Post-decrement
            result = num--;
            System.out.println("Post-decrement result: " + result);
    
            sc.close();
        }
    }

    Input:

    Enter a number: 10

    Output:

    Unary plus result: 10
    Unary minus result: -10
    Pre-increment result: 11
    Post-increment result: 11
    Pre-decrement result: 11
    Post-decrement result: 10

    Java Assignment Operators

    In Java, operators form the basic building blocks of programming, allowing developers to perform various types of calculations, comparisons, and logical operations. Assignment operators specifically help assign values to variables. They have right-to-left associativity, meaning the value on the right side of the operator is assigned to the variable on the left. Let’s dive into the different types of assignment operators used in Java.

    Types of Assignment Operators:

    1. Simple Assignment Operator (=): The simple assignment operator is used to assign a value to a variable. The operand on the right-hand side must match the data type of the variable on the left-hand side.

    Syntax:

    variable = value;

    Example:

    public class AssignmentOperatorExample {
        public static void main(String[] args) {
            int num = 15;
            String name = "Java";
    
            System.out.println("num is assigned: " + num);
            System.out.println("name is assigned: " + name);
        }
    }

    Output:

    num is assigned: 15
    name is assigned: Java

    2. Add and Assign (+=): The compound += operator adds the value on the right to the value of the variable on the left and then assigns the result back to the variable on the left.

    Syntax:

    variable += value;

    Example:

    public class AddAssignExample {
        public static void main(String[] args) {
            int num1 = 5, num2 = 3;
            num1 += num2;
            System.out.println("After adding: " + num1);
        }
    }

    Output:

    After adding: 8

    Note: This operator performs implicit type conversion if necessary. For instance, adding a double to an int using += will result in automatic narrowing conversion.

    3. Subtract and Assign (-=): The compound -= operator subtracts the value on the right from the value on the left and then assigns the result back to the variable on the left.

    Syntax:

    variable -= value;

    Example:

    public class SubtractAssignExample {
        public static void main(String[] args) {
            int num1 = 10, num2 = 4;
            num1 -= num2;
            System.out.println("After subtracting: " + num1);
        }
    }

    Output:

    After subtracting: 6

    4. Multiply and Assign (*=): This operator multiplies the current value of the variable by the value on the right and assigns the result back to the variable on the left.

    Syntax:

    variable *= value;
    a *= 2;  // Equivalent to a = a * 2

    Example:

    public class MultiplyAssignExample {
        public static void main(String[] args) {
            int num1 = 4, num2 = 3;
            num1 *= num2;
            System.out.println("After multiplying: " + num1);
        }
    }

    Output:

    After multiplying: 12

    5. Divide and Assign (/=): This operator divides the current value of the variable by the value on the right and assigns the result back to the variable on the left.

    Syntax:

    variable /= value;

    Example:

    public class DivideAssignExample {
        public static void main(String[] args) {
            int num1 = 20, num2 = 4;
            num1 /= num2;
            System.out.println("After dividing: " + num1);
        }
    }

    Output:

    After dividing: 5

    6. Modulus and Assign (%=): This operator performs division and assigns the remainder back to the variable on the left.

    Syntax:

    variable %= value;

    Example:

    public class ModulusAssignExample {
        public static void main(String[] args) {
            int num1 = 17, num2 = 5;
            num1 %= num2;
            System.out.println("After modulus: " + num1);
        }
    }

    Output:

    After modulus: 2

    Java Relational Operators

    Relational operators in Java are binary operators used to compare two operands and return a boolean value indicating the result of the comparison. These operators are often used in control flow statements such as loops and conditionals.

    General Syntax:

    variable1 relation_operator variable2

    1. Equal to (==)

    The == operator checks if two operands are equal. If they are, it returns true; otherwise, it returns false.

    Syntax:

    var1 == var2

    Example:

    int var1 = 5, var2 = 10, var3 = 5;
    
    System.out.println("var1 == var2: " + (var1 == var2));  // false
    System.out.println("var1 == var3: " + (var1 == var3));  // true

    Output:

    var1 == var2: false
    var1 == var3: true

    2. Not Equal to (!=)

    The != operator checks if two operands are not equal. It returns true if the operands are unequal, and false if they are equal.

    Syntax:

    var1 != var2

    Loop Variables (Block Scope)

    Variables declared inside a loop have scope limited to the loop. They cannot be accessed outside the loop.

    Example:

    int var1 = 5, var2 = 10, var3 = 5;
    
    System.out.println("var1 != var2: " + (var1 != var2));  // true
    System.out.println("var1 != var3: " + (var1 != var3));  // false

    Output:

    var1 != var2: true
    var1 != var3: false

    3. Greater than (>)

    The > operator checks if the left operand is greater than the right operand. If true, it returns true; otherwise, false.

    Syntax:

    var1 > var2

    Example:

    int var1 = 30, var2 = 20;
    
    System.out.println("var1 > var2: " + (var1 > var2));  // true

    Output:

    var1 > var2: true

    4. Less than (<)

    The < operator checks if the left operand is less than the right operand.

    Syntax:

    var1 < var2

    Example:

    int var1 = 10, var2 = 20;
    
    System.out.println("var1 < var2: " + (var1 < var2));  // true

    Output:

    var1 < var2: true

    5. Greater than or equal to (>=)

    The >= operator checks if the left operand is greater than or equal to the right operand.

    Syntax:

    var1 >= var2

    Example:

    int var1 = 20, var2 = 20;
    
    System.out.println("var1 >= var2: " + (var1 >= var2));  // true

    Output:

    var1 >= var2: true

    6. Less than or equal to (<=)

    The <= operator checks if the left operand is less than or equal to the right operand.

    Syntax:

    var1 <= var2

    Example:

    int var1 = 10, var2 = 10;
    
    System.out.println("var1 <= var2: " + (var1 <= var2));  // true

    Output:

    var1 <= var2: true

    Java Logical Operators

    Logical operators are used to perform logical operations such as “AND,” “OR,” and “NOT,” similar to the functions of AND and OR gates in digital electronics. They combine two or more conditions or reverse the outcome of a given condition. An important point to remember is that in the case of the AND operator, the second condition is not evaluated if the first is false, while in the case of the OR operator, the second condition is not evaluated if the first is true. This feature is known as short-circuiting. Logical operators are commonly used in decision-making when multiple conditions need to be checked.

    Logical Operators in Java

    Let’s go through an example with some common logical operators in Java. Assume we have the following variables:

    Syntax:

    int a = 10;
    int b = 20;
    int c = 30;

    AND Operator (&&):

    This operator returns true if both conditions are true. Otherwise, it returns false.

    Example:

    • Condition 1: c > a (true)
    • Condition 2: c > b (true)

    Since both conditions are true, the result will be true.

    Example:

    if (c > a && c > b) {
        System.out.println("Both conditions are true");
    } else {
        System.out.println("At least one condition is false");
    }

    Output:

    Both conditions are true

    OR Operator (||):

    This operator returns true if at least one of the conditions is true. If both conditions are false, the result is false.

    Example:

    • Condition 1: c > a (true)
    • Condition 2: c < b (false)

    Since one of the conditions is true, the result will be true.

    if (c > a || c < b) {
        System.out.println("At least one condition is true");
    } else {
        System.out.println("Both conditions are false");
    }

    Output:

    At least one condition is true

    NOT Operator (!):

    This unary operator returns the opposite of the condition. If the condition is true, it returns false, and vice versa.

    Example:

    • Condition: c > a (true)
    • Applying NOT: !(c > a) (false)
    System.out.println(!(c > a));  // false
    System.out.println(!(c < a));  // true

    Output:

    false
    true

    Short-Circuiting in AND Operator:

    In cases where the first condition of an AND operation is false, the second condition will not be evaluated.

    Example:

    int a = 10, b = 20, c = 15;
    
    if ((a > c) && (++b > c)) {
        System.out.println("Condition met");
    }
    System.out.println("Value of b: " + b);

    Output:

    Value of b: 20

    Short-Circuiting in AND Operator:

    In cases where the first condition of an AND operation is false, the second condition will not be evaluated.

    Example:

    int a = 10, b = 20, c = 15;
    
    if ((a > c) && (++b > c)) {
        System.out.println("Condition met");
    }
    System.out.println("Value of b: " + b);

    Output:

    Value of b: 20

    Short-Circuiting in OR Operator:

    In cases where the first condition of an OR operation is true, the second condition will not be evaluated.

    Example:

    int a = 10, b = 20, c = 15;
    
    if ((a < c) || (++b < c)) {
        System.out.println("Condition met");
    }
    System.out.println("Value of b: " + b);

    Output:

    Condition met
    Value of b: 20

    Boolean Values and Logical Operators:

    Logical operators can also be applied directly to boolean values. For example:

    Example:

    boolean a = true;
    boolean b = false;
    
    System.out.println("a && b: " + (a && b)); // false
    System.out.println("a || b: " + (a || b)); // true
    System.out.println("!a: " + !a);           // false
    System.out.println("!b: " + !b);           // true

    Output:

    a && b: false
    a || b: true
    !a: false
    !b: true

    Java Ternary Operator

    The ternary operator is the only operator in Java that takes three operands. It serves as a concise alternative to the traditional if-else statement, allowing conditional logic in a single line. Although it operates similarly to if-else, it helps to reduce code size and improve readability.

    Syntax:

    variable = (condition) ? expression1 : expression2;

    This syntax translates to:

    • If condition is trueexpression1 is executed.
    • If condition is falseexpression2 is executed.

    Here’s the equivalent structure using an if-else statement:

    if(condition) {
        variable = expression1;
    } else {
        variable = expression2;
    }

    Example:

    int num1 = 10;
    int num2 = 20;
    int result = (num1 > num2) ? (num1 + num2) : (num1 - num2);

    Since num1 < num2, the second expression is executed, so:

    Syntax:

    result = num1 - num2 = -10;

    Example:

    class TernaryExample {
        public static void main(String[] args) {
            int n1 = 5, n2 = 10, max;
    
            System.out.println("First number: " + n1);
            System.out.println("Second number: " + n2);
    
            // Using ternary operator to find the maximum
            max = (n1 > n2) ? n1 : n2;
    
            System.out.println("The maximum number is: " + max);
        }
    }

    Output:

    First number: 5
    Second number: 10
    The maximum number is: 10

    Bitwise operators

    Bitwise operators are used to manipulate individual bits of a number. They are particularly useful when optimizing performance in certain cases, as they directly operate on the binary representation of numbers. Bitwise operators can be used with any integral type (e.g., charshortint). They are often employed when performing operations on Binary Indexed Trees (BITs).

    Here are the common bitwise operators in Java:

    1. Bitwise OR (|)

    The Bitwise OR operator is represented by the symbol |. It compares the corresponding bits of two operands, and if either of the bits is 1, the result is 1; otherwise, the result is 0.
    Example:

    a = 6 = 0110 (In Binary)
    b = 9 = 1001 (In Binary)
    
    Bitwise OR Operation:
      0110
    | 1001
    _________
      1111  = 15 (In decimal)

    2. Bitwise AND (&)

    The Bitwise AND operator is represented by &. It compares the corresponding bits of two operands, and if both bits are 1, the result is 1; otherwise, the result is 0.
    Example:

    a = 6 = 0110 (In Binary)
    b = 9 = 1001 (In Binary)
    
    Bitwise AND Operation:
      0110
    & 1001
    _________
      0000  = 0 (In decimal)

    3. Bitwise XOR (^)

    The Bitwise XOR operator is represented by ^. It compares the corresponding bits of two operands, and if the bits are different, the result is 1; otherwise, the result is 0.
    Example:

    a = 6 = 0110 (In Binary)
    b = 9 = 1001 (In Binary)
    
    Bitwise XOR Operation:
      0110
    ^ 1001
    _________
      1111  = 15 (In decimal)

    4. Bitwise Complement (~)

    The Bitwise NOT (complement) operator is represented by ~. It inverts all the bits of the operand, changing every 1 to 0 and every 0 to 1.
    Example:

    a = 6 = 0110 (In Binary)
    
    Bitwise NOT Operation:
    ~ 0110
    _________
      1001  = -7 (In decimal, as it returns the two's complement)

    Code Example:

    import java.util.Scanner;
    
    public class BitwiseOperators {
        public static void main(String[] args) {
            Scanner input = new Scanner(System.in);
    
            System.out.print("Enter first number: ");
            int num1 = input.nextInt();
    
            System.out.print("Enter second number: ");
            int num2 = input.nextInt();
    
            System.out.println("Bitwise AND: " + (num1 & num2));
            System.out.println("Bitwise OR: " + (num1 | num2));
            System.out.println("Bitwise XOR: " + (num1 ^ num2));
            System.out.println("Bitwise NOT: " + (~num1));
            System.out.println("Bitwise Left Shift: " + (num1 << 2));
            System.out.println("Bitwise Right Shift: " + (num1 >> 2));
            System.out.println("Bitwise Unsigned Right Shift: " + (num1 >>> 2));
    
            input.close();
        }
    }

    Input:

    Enter first number: 4
    Enter second number: 10

    Output:

    Bitwise AND: 0
    Bitwise OR: 14
    Bitwise XOR: 14
    Bitwise NOT: -5
    Bitwise Left Shift: 16
    Bitwise Right Shift: 1
    Bitwise Unsigned Right Shift: 1
  • Flow Control in java

    Decision Making in Java (if, if-else, switch, break, continue, jump).

    Decision-making in programming is like making decisions in real life. When coding, we often need to execute certain blocks of code only if specific conditions are met.

    Java provides several control statements to control the flow of a program based on conditions. These statements direct the flow of execution, allowing it to branch and continue based on the program’s state.

    Java’s Selection Statements:
    • if
    • if-else
    • nested-if
    • if-else-if
    • switch-case
    • jump (break, continue, return)

    1. if Statement:

    The simplest decision-making statement. It is used to decide whether a certain block of code will be executed or not, depending on whether the condition is true.

    Syntax:

    if(condition) {
       // Statements to execute if the condition is true
    }

    Here, the condition evaluates to either true or false. If true, the block of statements inside the if block is executed.

    If we do not use curly braces ({}), only the first statement after the if will be considered part of the if block.

    Example:

    class IfDemo {
        public static void main(String args[]) {
            int i = 10;
    
            if (i < 15)
                System.out.println("Inside If block"); // Part of if block
            System.out.println("10 is less than 15");  // Outside of if block
            System.out.println("I am not in if block");
        }
    }

    Output:

    Inside If block
    10 is less than 15
    I am not in if block

    2. if-else Statement:

    The if statement alone can execute code when a condition is true. But what if we want to execute something else when the condition is false? This is where the else statement comes in.

    Syntax:

    if (condition) {
        // Executes if condition is true
    } else {
        // Executes if condition is false
    }

    Example:

    class IfElseDemo {
        public static void main(String args[]) {
            int i = 10;
    
            if (i < 15)
                System.out.println("i is smaller than 15");
            else
                System.out.println("i is greater than 15");
        }
    }

    Output:

    i is smaller than 15

    3. nested-if Statement:

    nested-if is an if statement inside another if. This allows you to perform more complex decision-making scenarios.

    Syntax:

    if (condition1) {
       if (condition2) {
          // Executes when both conditions are true
       }
    }

    Example:

    class NestedIfDemo {
        public static void main(String args[]) {
            int i = 10;
    
            if (i == 10 || i < 15) {
                if (i < 15)
                    System.out.println("i is smaller than 15");
    
                if (i < 12)
                    System.out.println("i is smaller than 12 too");
            } else {
                System.out.println("i is greater than 15");
            }
        }
    }

    Output:

    i is smaller than 15
    i is smaller than 12 too

    4. if-else-if Ladder:

    The if-else-if ladder allows you to test multiple conditions. The conditions are evaluated from top to bottom, and as soon as one of them is true, its corresponding block is executed, and the rest are skipped.

    Syntax:

    if (condition1) {
        // Executes if condition1 is true
    } else if (condition2) {
        // Executes if condition2 is true
    } else {
        // Executes if none of the above conditions are true
    }

    Example:

    class IfElseIfDemo {
        public static void main(String args[]) {
            int i = 20;
    
            if (i == 10)
                System.out.println("i is 10");
            else if (i == 15)
                System.out.println("i is 15");
            else if (i == 20)
                System.out.println("i is 20");
            else
                System.out.println("i is not present");
        }
    }

    Output:

    i is 20

    5. switch-case Statement:

    The switch statement allows you to branch execution based on the value of an expression. It’s like having multiple if-else blocks for a single expression.

    Syntax:

    switch (expression) {
      case value1:
        // Code to be executed if expression equals value1
        break;
      case value2:
        // Code to be executed if expression equals value2
        break;
      // Add more cases as needed
      default:
        // Code to be executed if none of the cases match
    }

    Example:

    class SwitchDemo {
        public static void main(String[] args) {
            int num = 20;
            switch (num) {
              case 5:
                System.out.println("It is 5");
                break;
              case 10:
                System.out.println("It is 10");
                break;
              case 15:
                System.out.println("It is 15");
                break;
              case 20:
                System.out.println("It is 20");
                break;
              default:
                System.out.println("Not present");
            }
        }
    }

    Output:

    It is 20

    6. Jump Statements:

    Java has three main jump statements:

    • break: Used to exit a loop or a switch statement.
    • continue: Skips the remaining code in the current iteration of a loop and starts the next iteration.
    • return: Exits from the current method and passes control back to the caller.

    Example of continue:

    class ContinueDemo {
        public static void main(String args[]) {
            for (int i = 0; i < 10; i++) {
                if (i % 2 == 0)
                    continue;
    
                System.out.print(i + " ");
            }
        }
    }

    Output:

    1 3 5 7 9

    If Statements with Examples

    Decision Making in Java allows you to execute a specific block of code based on certain conditions. It’s a key part of controlling the flow of a program, similar to how we make decisions in real life.

    Java’s if Statement:

    The if statement is the simplest form of decision-making. It allows you to execute a block of code if a specific condition evaluates to true.

    Syntax:

    if (condition) {
       // Statements to execute if the condition is true
    }

    How It Works:

    1. Control enters the if block.
    2. The condition is evaluated.

    • If the condition is true, the block of code within the if statement is executed.
    • If the condition is false, the control moves to the next part of the program.

    Example:

    class IfDemo {
        public static void main(String args[]) {
            int i = 10;
    
            if (i < 15)
                System.out.println("10 is less than 15");
    
            System.out.println("Outside if-block");
        }
    }

    Output:

    10 is less than 15
    Outside if-block
    Java if-else Statement

    Decision-making in Java allows for the execution of certain blocks of code based on specific conditions. The if statement tells us that if a condition is true, it will execute a block of statements; otherwise, it won’t. By combining if and else, you can handle both outcomes—what happens when the condition is true and when it’s false.

    Understanding if-else in Java:

    The if-else statement is used to control the flow of your program by executing code based on whether a condition is true or false.

    Syntax of if-else Statement:

    if (condition) {
        // Executes this block if the condition is true
    } else {
        // Executes this block if the condition is false
    }

    Example Program:

    // Java program to demonstrate if-else statement
    
    class IfElseDemo {
        public static void main(String args[]) {
            int i = 20;
    
            if (i < 15) {
                System.out.println("i is smaller than 15");
            } else {
                System.out.println("i is greater than 15");
            }
    
            System.out.println("Outside if-else block");
        }
    }

    Output:

    i is greater than 15
    Outside if-else block

    Java if-else-if Ladder

    Decision-making in Java allows us to write condition-based statements that execute particular blocks of code depending on specific conditions. The if-else-if ladder is used to handle multiple conditions by checking each one sequentially. As soon as a condition evaluates to true, its corresponding block is executed, and the rest of the ladder is ignored. If none of the conditions are true, the final else block is executed.

    Syntax:

    if (condition) {
        // Executes if condition 1 is true
    } else if (condition) {
        // Executes if condition 2 is true
    }
    .
    .
    else {
        // Executes if no conditions are true
    }

    Working of if-else-if Ladder:

    1. Control enters the if block.
    2. The first condition is evaluated:If true, the associated block is executed, and the rest of the ladder is skipped.
    If false, the flow moves to the next condition.
    3. This process repeats for each condition in the ladder.
    4. If none of the conditions are met, the else block is executed.

    Example :

    // Java program to demonstrate if-else-if ladder
    
    class IfElseIfLadderDemo {
        public static void main(String[] args) {
            // Initializing expression
            int i = 20;
    
            // Condition 1
            if (i == 10) {
                System.out.println("i is 10");
            }
            // Condition 2
            else if (i == 15) {
                System.out.println("i is 15");
            }
            // Condition 3
            else if (i == 20) {
                System.out.println("i is 20");
            }
            // Default case
            else {
                System.out.println("i is not present");
            }
    
            System.out.println("Outside if-else-if ladder");
        }
    }

    Output:

    i is 20
    Outside if-else-if ladder

    Loops in Java

    Looping is an essential feature in programming that allows a set of instructions to be executed repeatedly as long as a specified condition evaluates to true. In Java, loops are implemented in three different ways, each with a unique syntax and method of checking conditions.

    while Loop

    Looping is an essential feature in programming that allows a set of instructions to be executed repeatedly as long as a specified condition evaluates to true. In Java, loops are implemented in three different ways, each with a unique syntax and method of checking conditions.

    while (boolean condition) {
        // Loop body
    }

    Example:

    public class WhileLoopExample {
        public static void main(String[] args) {
            int i = 0;
            while (i <= 10) {
                System.out.println(i);
                i++;
            }
        }
    }

    Output:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Working of while loop:

    • The loop starts by checking the boolean condition.
    • If the condition is true, the loop body is executed.
    • After each execution, the condition is rechecked before the next iteration.
    • The loop terminates when the condition evaluates to false.

    This is an entry control loop, meaning the condition is checked before the loop body is executed.

    Java do-while loop with Examples

    Loops are a fundamental concept in programming, used when we need to execute a block of code repeatedly. The do-while loop is an example of an Exit Control Loop in Java, meaning the loop body will always execute at least once, as the condition is checked after the loop body has been executed, unlike in for and while loops where the condition is checked beforehand.

    Syntax of do-while Loop:

    do {
        // Loop body
        update_expression;
    } while (test_expression);

    Output:

    Print statement

    For-each loop in Java

    The for-each loop in Java, introduced in Java 5, is a simplified way to traverse arrays and collections. It provides a cleaner and more readable syntax when you need to access each element of an array or a collection like ArrayList.

    Key Features of the For-Each Loop:
    • It begins with the keyword for, similar to a regular for-loop.
    • Instead of initializing and maintaining a loop counter, you declare a variable (of the same type as the array elements) that iterates through each element.
    • The loop body uses this variable directly, without needing to reference an array index.

    The for-each loop is particularly useful for iterating through arrays or classes from the Java Collection Framework, such as ArrayList.

    Syntax:

    for (type var : array) {
        // Statements using var
    }

    Example: Simple Array Traversal using For-Each Loop

    public class Easy {
        public static void main(String[] args) {
            // Array declaration
            int[] ar = {10, 50, 60, 80, 90};
    
            // For-each loop to iterate through array
            for (int element : ar) {
                System.out.print(element + " ");
            }
        }
    }

    Output:

    10 50 60 80 90

    Equivalent Syntax with Regular For Loop:

    for (int i = 0; i < ar.length; i++) {
        int element = ar[i];
        // Statements using element
    }

    Example: Finding the Highest Score using For-Each Loop

    // Java program to illustrate for-each loop
    public class ForEachExample {
        public static void main(String[] args) {
            int[] marks = {125, 132, 95, 116, 110};
    
            int highestMarks = maximum(marks);
            System.out.println("The highest score is " + highestMarks);
        }
    
        public static int maximum(int[] numbers) {
            int maxSoFar = numbers[0];
    
            // For-each loop to find the maximum value
            for (int num : numbers) {
                if (num > maxSoFar) {
                    maxSoFar = num;
                }
            }
            return maxSoFar;
        }
    }

    Output:

    The highest score is 132
    Limitations of the For-Each Loop

    1. Modifying Array Elements: For-each loops don’t allow modification of array elements. Changing the loop variable does not affect the array:

    for (int num : marks) {
        num = num * 2;  // Only changes num, not the actual array element
    }

    2. No Access to  Index: You can’t get the index of the current element using a for-each loop:

    for (int num : numbers) {
        if (num == target) {
            return ???;  // No index available
        }
    }

    3. Only Forward Iteration: For-each loops can only iterate forward, making it impossible to iterate in reverse order:

    for (int i = numbers.length - 1; i >= 0; i--) {
        System.out.println(numbers[i]);  // Cannot be done using for-each
    }

    4. Handling Multiple Decision Statements: For-each loops struggle to process multiple decision-making conditions:

    for (int i = 0; i < numbers.length; i++) {
        if (numbers[i] == arr[i]) {
            // Hard to convert this to a for-each loop
        }
    }

    Break Statement in Java

    The break statement in Java is a control statement used to terminate the loop prematurely. Once the break statement is encountered, the loop terminates immediately, and the control moves to the next statement following the loop.

    Syntax:

    break;
    When to Use the Break Statement:

    1. When the exact number of iterations is not known in advance.
    2. When you want to exit the loop based on some condition during iteration.

    Use Cases for Break:

    1. Terminating a sequence in a switch statement.
    2. Exiting a loop.
    3. Serving as a structured alternative to goto.

    Example: Break in a Switch Statement

    // Example of using break statement in switch
    public class SwitchBreakExample {
        public static void main(String[] args) {
            // Assigning n an integer value
            int n = 1;
    
            // Passing n to switch
            // The case matching n will execute and terminate due to break
            switch (n) {
                case 1:
                    System.out.println("Hello World");
                    break;
                case 2:
                    System.out.println("Second Case");
                    break;
                default:
                    System.out.println("Default Case");
            }
        }
    }

    Output:

    Hello World
    Using Break to Exit a Loop

    With the break statement, you can force the loop to terminate immediately, skipping the remaining iterations. It’s commonly used when you want to stop the loop based on a specific condition.

    Example: Breaking out of a Loop

    // Java program to illustrate using break to exit a loop
    public class BreakLoopDemo {
        public static void main(String[] args) {
            // The loop runs from 0 to 9
            for (int i = 0; i < 10; i++) {
                // Terminate loop when i equals 5
                if (i == 5)
                    break;
    
                System.out.println("i: " + i);
            }
            System.out.println("Loop complete.");
        }
    }

    Output:

    i: 0
    i: 1
    i: 2
    i: 3
    i: 4
    Loop complete.
    • Time Complexity: O(1)
    • Auxiliary Space: O(1)

    Using Break as a Form of Goto

    Java doesn’t support the goto statement because it allows jumping in an unstructured manner, leading to complex and unreadable code. Instead, Java uses labels to identify a block of code. You can use the break statement to jump out of a labeled block.

    Syntax:

    label:
    {
      // Code block
      statement1;
      statement2;
      // Break can be used to exit the block
      break label;
    }

    Example: Break with a Label

    // Java program to illustrate using break with label
    public class BreakLabelDemo {
        public static void main(String[] args) {
            boolean condition = true;
    
            // Label first
            first: {
                second: {
                    third: {
                        // Before break
                        System.out.println("Before the break statement");
    
                        // If condition is true, break out of the second block
                        if (condition)
                            break second;
    
                        System.out.println("This won't execute.");
                    }
                    System.out.println("This won't execute.");
                }
    
                // After exiting the second block
                System.out.println("This is after second block.");
            }
        }
    }

    Output:

    Before the break statement
    This is after second block

    return keyword in Java

    In Java, the scope of a variable refers to the region in the code where the variable is accessible. Java has lexical (static) scoping, meaning the scope of a variable is determined at compile time and is not dependent on the function call stack. The scope rules in Java can be broadly classified into three categories based on where the variables are declared.

    1. Member Variables (Class-Level Scope)
    2. Local Variables (Method-Level Scope)
    3. Block Variables (Loop or Block-Level Scope)

    1. Member Variables (Class-Level Scope)

    Member variables are declared inside a class but outside any method, constructor, or block. They can be accessed anywhere within the class and can have different access levels (e.g., publicprivateprotected, or default). Access to member variables outside the class depends on the access modifier used.

    Example:

    public class Test {
        // Member variables
        int a;                  // Default access modifier
        private String b;        // Private member variable
        char c;                  // Default access modifier
    
        void method1() {
            // Member variables can be accessed here
            System.out.println(a);
            System.out.println(b);
        }
    
        int method2() {
            return a;
        }
    }
    • Public: Accessible within the class, in subclasses, and outside the class.
    • Protected: Accessible within the class and in subclasses but not outside the package.
    • Default (no modifier): Accessible within the same package but not outside it.
    • Private: Only accessible within the class.


    2. Local Variables (Method-Level Scope)

    Local variables are declared inside a method or constructor and are only accessible within that method. They must be initialized before use, and their lifetime is limited to the method’s execution. Once the method finishes, local variables are destroyed.

    Example:

    public class Test {
        void method1() {
            // Local variable
            int x = 10;
            System.out.println(x);  // Accessible inside the method
        }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.method1();
        }
    }

    Local variables do not retain their values once the method completes, and they are recreated each time the method is invoked.

    3. Block Variables (Loop or Block-Level Scope)

    Variables declared inside a block (within curly braces {}) are only accessible within that block. Once the block is exited, these variables are out of scope and cannot be accessed. This applies to variables declared inside loops or conditionals.

    Example of Block-Level Scope:

    public class Test {
        public static void main(String[] args) {
            {
                int x = 10;  // x is only accessible inside this block
                System.out.println(x);
            }
    
            // Uncommenting the following line will cause an error
            // System.out.println(x);  // x is out of scope here
        }
    }

    Loop Variables (Block Scope)

    Variables declared inside a loop have scope limited to the loop. They cannot be accessed outside the loop.

    Example:

    class Test {
        public static void main(String[] args) {
            for (int x = 0; x < 4; x++) {
                System.out.println(x);  // x is accessible inside the loop
            }
    
            // Uncommenting the following line will result in an error
            // System.out.println(x);  // x is out of scope here
        }
    }

    If you need to access a loop variable outside the loop, declare it before the loop:

    class Test {
        public static void main(String[] args) {
            int x;
            for (x = 0; x < 4; x++) {
                System.out.println(x);
            }
            System.out.println(x);  // x is accessible outside the loop
        }
    }

    Output:

    0
    1
    2
    3
    4

    Loop Variable Scope with Overlapping Names

    In Java, you cannot declare two variables with the same name within the same scope. However, in languages like C++, it’s possible to have the same variable name in nested scopes, which is not allowed in Java.

    Incorrect Example in Java (compilation error):

    class Test {
        public static void main(String[] args) {
            int a = 5;
            for (int a = 0; a < 5; a++) {  // Error: Variable 'a' is already defined
                System.out.println(a);
            }
        }
    }

    Output:

    Error: variable 'a' is already defined

    Valid Example with Loop Variable Declaration After Loop

    To avoid such errors, you can declare a variable outside the loop and use it after the loop finishes.

    Example:

    class Test {
        public static void main(String[] args) {
            for (int i = 1; i <= 10; i++) {
                System.out.println(i);  // Loop variable i
            }
    
            int i = 20;  // Declare i after the loop
            System.out.println(i);  // Access new i outside the loop
        }
    }

    Output:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    20
  • Input/Output in Java

    How to Take Input from User in Java

    Java provides multiple ways to take input from users through its I/O (Input/Output) package. These streams facilitate the reading of characters, data types, and objects from input devices such as keyboards or files. The most common ways to capture user input in Java are through the BufferedReader class and the Scanner class.

    Methods to Take Input in Java

    There are two primary ways to get input from the user or a file in Java:

    1. Using BufferedReader Class
    2. Using Scanner Class

    1. Using BufferedReader Class to Take Input in Java

    The BufferedReader class is part of Java’s I/O package and is used for reading streams of characters. The readLine() method of this class reads input as a string. InputStreamReader is often used in conjunction with BufferedReader to convert byte streams into character streams, enabling character-based input reading.

    Note: BufferedReader can throw checked exceptions, which need to be handled using try-catch blocks or by declaring them with throws.

    Example of Taking Input Using BufferedReader:

    import java.io.*;
    
    public class InputExample {
        public static void main(String[] args) throws IOException {
            // Creating a BufferedReader object
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    
            // Reading string input
            System.out.println("Enter a string:");
            String str = reader.readLine();
    
            // Reading integer input
            System.out.println("Enter an integer:");
            int num = Integer.parseInt(reader.readLine());
    
            // Output the values
            System.out.println("Entered String: " + str);
            System.out.println("Entered Integer: " + num);
        }
    }

    Output:

    Enter a string:
    John Doe
    Enter an integer:
    123
    Entered String: John Doe
    Entered Integer: 123

    Alternative Example of BufferedReader Input:

    import java.io.*;
    
    public class Example {
        public static void main(String[] args) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            try {
                System.out.println("Enter your name:");
                String name = reader.readLine();  // Reading string input
                System.out.println("Name: " + name);
            } catch (IOException e) {
                System.out.println("An error occurred: " + e.getMessage());
            }
        }
    }

    Sample Output:

    Enter your name:
    John
    Name: John

    2. Using Scanner Class for Taking Input in Java

    The Scanner class is an advanced and user-friendly input class that is part of Java’s util package. It simplifies input handling by providing various methods like nextInt()nextFloat()next(), and nextLine(). These methods allow you to directly capture input without explicit parsing.

    Advantages of Scanner:

    • Easier to use than BufferedReader.
    • Supports multiple data types (integers, floats, strings, etc.) directly.
    • No need for throws declarations as no checked exceptions are thrown.
    • Provides methods to read formatted input easily.

    Example of Taking Input Using Scanner:

    import java.util.Scanner;
    
    class Main {
        public static void main(String[] args) {
            // Create Scanner object
            Scanner sc = new Scanner(System.in);
    
            // Input a String (single word)
            String word = sc.next();
            System.out.println("Entered word: " + word);
    
            // Input a String (full sentence)
            sc.nextLine();  // Clear the buffer
            String sentence = sc.nextLine();
            System.out.println("Entered sentence: " + sentence);
    
            // Input an Integer
            int num = sc.nextInt();
            System.out.println("Entered Integer: " + num);
    
            // Input a floating-point number
            float f = sc.nextFloat();
            System.out.println("Entered Float: " + f);
        }
    }

    Output:

    John
    Hello World
    123
    45.67
    Entered word: John
    Entered sentence: Hello World
    Entered Integer: 123
    Entered Float: 45.67

    Another Example:

    import java.util.Scanner;
    
    class Main {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
    
            System.out.println("Enter your name:");
            String name = sc.nextLine();
    
            System.out.println("Enter your roll number:");
            int rollNo = sc.nextInt();
    
            System.out.println("Enter your marks:");
            float marks = sc.nextFloat();
    
            // Output the entered data
            System.out.println("Name: " + name);
            System.out.println("Roll Number: " + rollNo);
            System.out.println("Marks: " + marks);
        }
    }

    Output:

    Enter your name:
    Alice
    Enter your roll number:
    10
    Enter your marks:
    89.5
    Name: Alice
    Roll Number: 10
    Marks: 89.5
    Differences Between BufferedReader and Scanner

    1. BufferedReader is a basic way to read input, typically used for reading streams of characters. It is faster than Scanner as it doesn’t perform any post-processing or parsing.
    2. Scanner is more user-friendly for simple input, especially when you need to read different data types. However, Scanner is slower because it performs input parsing internally (like nextInt(), nextFloat()).
    3. BufferedReader allows you to specify the size of the input stream to be read, making it more flexible when handling large input.
    4. BufferedReader is synchronized, making it suitable for multi-threaded applications, while Scanner is not.
    5. Scanner is typically preferred for smaller inputs due to its simplicity, while BufferedReader is favored for larger inputs due to better performance.

    Scanner Class in Java

    The Scanner class in Java is part of the java.util package and is used to obtain input from the user, including primitive data types like intdouble, and boolean, as well as strings.

    While the Scanner class is the simplest way to read input, it’s not the most efficient when performance is critical, such as in competitive programming, due to additional overhead.

    Input Types in Java Scanner

    The Scanner class can take various types of input from the user. Some of the commonly used methods for extracting data from an input stream are listed below:

    MethodDescription
    nextBoolean()Reads a Boolean value
    nextByte()Reads a Byte value
    nextDouble()Reads a Double value
    nextFloat()Reads a Float value
    nextInt()Reads an Integer value
    nextLine()Reads an entire line as a String
    nextLong()Reads a Long value
    nextShort()Reads a Short value

    Example 1: Reading Data of Various Types

    Below is a sample code that demonstrates how to read different types of data using the Scanner class.

    // Java program to read data of various types using Scanner class.
    import java.util.Scanner;
    
    public class ScannerDemo1 {
        public static void main(String[] args) {
            // Creating a Scanner object for input
            Scanner sc = new Scanner(System.in);
    
            // Reading a String input
            String name = sc.nextLine();
    
            // Reading a Character input
            char gender = sc.next().charAt(0);
    
            // Reading numerical data
            int age = sc.nextInt();
            long mobileNo = sc.nextLong();
            double cgpa = sc.nextDouble();
    
            // Outputting the values to check input
            System.out.println("Name: " + name);
            System.out.println("Gender: " + gender);
            System.out.println("Age: " + age);
            System.out.println("Mobile Number: " + mobileNo);
            System.out.println("CGPA: " + cgpa);
        }
    }

    Input:

    Alice
    F
    25
    9876543210
    8.9

    Output:

    Name: Alice
    Gender: F
    Age: 25
    Mobile Number: 9876543210
    CGPA: 8.9
    Checking Input Type with hasNextXYZ() Methods

    In some cases, we need to verify the type of the next input or whether the input has ended (e.g., when encountering the EOF marker). The hasNextXYZ() methods allow us to check if the next token is of the desired type. Here, XYZ represents the data type of interest.

    For instance, we can check for an integer using hasNextInt() or a string using hasNextLine(). Similarly, to check for a single character, we can use hasNext().charAt(0).

    Example 2: Calculating the Mean of Numbers

    // Java program to read values using Scanner and calculate their mean.
    import java.util.Scanner;
    
    public class ScannerDemo2 {
        public static void main(String[] args) {
            // Initialize Scanner object
            Scanner sc = new Scanner(System.in);
    
            // Initialize sum and count variables
            int sum = 0, count = 0;
    
            // Read integers until non-integer input is encountered
            while (sc.hasNextInt()) {
                int num = sc.nextInt();
                sum += num;
                count++;
            }
    
            // Calculate and print the mean
            if (count > 0) {
                int mean = sum / count;
                System.out.println("Mean: " + mean);
            } else {
                System.out.println("No integers were input. Mean cannot be calculated.");
            }
        }
    }

    Input:

    1 2 3 4 5

    Output:

    Mean: 3

    Key Points About the Scanner Class

    1. Creating a Scanner Object: To create a Scanner object, we usually pass the predefined System.in object, which represents standard input. We can also pass a File object to read input from a file.
    2. Reading Numerical Values: For each primitive data type, there is a corresponding nextXYZ() method to read its value. For example, nextShort() reads a short value, and nextInt() reads an int.
    3. Reading Strings: To read strings, we use nextLine(). This method reads the entire line until a newline character is encountered.
    4. Reading a Single Character: To read a single character, we can use next().charAt(0). The next() method reads the next token (a word or symbol), and charAt(0) returns the first character of that token.
    5. How Scanner Reads Input: The Scanner class reads an entire line and divides it into tokens. Each token represents a meaningful piece of the input. For example, if the input string is “Hello world”, the Scanner object will treat “Hello” and “world” as two separate tokens, and we can iterate over these tokens using its various methods.

    Ways to read input from console in Java

    In Java, there are several ways to read input from the user in the command-line environment. Here are five methods:

    1. Using BufferedReader Class

    The BufferedReader class is the traditional way to take input, introduced in JDK 1.0. It wraps the standard input stream (System.in) in an InputStreamReader, which is further wrapped in a BufferedReader for efficient reading of input.

    Key Points:

    • Input is buffered for performance.
    • Syntax can be a bit more complex compared to newer methods.

    Example:

    // Java program to demonstrate BufferedReader
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class Test {
        public static void main(String[] args) throws IOException {
            // Using BufferedReader to get input
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    
            // Reading a string input
            String name = reader.readLine();
    
            // Printing the input
            System.out.println(name);
        }
    }

    Input:

    John

    Output:

    John
    2. Using Scanner Class

    The Scanner class, introduced in JDK 1.5, is one of the most commonly used methods to read input. It can parse primitive types and strings using regular expressions.

    Advantages:

    • Simplifies reading primitive types like intfloat, and more.
    • Regular expressions can be used to tokenize input.

    Example:

    import java.util.Scanner;
    
    public class InputExample {
    
        public static void main(String[] args) {
            // Create Scanner object
            Scanner scn = new Scanner(System.in);
    
            // Read a single word as a string
            System.out.println("Enter a word:");
            String word = scn.next();
    
            // Read a full line as a string
            System.out.println("Enter a sentence:");
            scn.nextLine(); // consume the leftover newline
            String sentence = scn.nextLine();
    
            // Read an integer
            System.out.println("Enter an integer:");
            int number = scn.nextInt();
    
            // Read a float value
            System.out.println("Enter a floating point number:");
            float decimal = scn.nextFloat();
    
            // Display the inputs
            System.out.println("Word: " + word);
            System.out.println("Sentence: " + sentence);
            System.out.println("Integer: "

    Input:

    Alice
    25
    4.5

    Output:

    You entered string: Alice
    You entered integer: 25
    You entered float: 4.5
    3. Using Console Class

    Introduced in JDK 1.6, the Console class is useful for reading input from the command line, especially for secure inputs like passwords (where input isn’t displayed). However, it may not work in non-interactive environments like IDEs.

    Advantages:

    • Can hide input (useful for password entry).
    • Supports formatted input and output.

    Example:

    // Java program to demonstrate Console class
    public class Sample {
        public static void main(String[] args) {
            // Using Console to get input
            String name = System.console().readLine();
            System.out.println("You entered string: " + name);
        }
    }

    Input:

    Michael

    Output:

    You entered string: Michael
    4. Using Command-Line Arguments

    This method uses the command-line arguments passed to the program at runtime. The arguments are stored as strings in the args[] array, and can be converted to other data types using methods like Integer.parseInt().

    Example:

    // Program to demonstrate command-line arguments
    class Hello {
        public static void main(String[] args) {
            // Check if arguments were passed
            if (args.length > 0) {
                System.out.println("The command line arguments are:");
                // Loop through the args array
                for (String val : args)
                    System.out.println(val);
            } else {
                System.out.println("No command line arguments found.");
            }
        }
    }

    Command Line Arguments:

    java Hello John Doe

    Output:

    The command line arguments are:
    John
    Doe
    5. Using DataInputStream Class

    The DataInputStream class allows reading primitive data types and strings from an input stream in a machine-independent way. This class is useful when working with binary input/output, and it was introduced in JDK 1.0.

    Key Points:

    • Reads binary data in a machine-independent format.
    • Often used with DataOutputStream for consistent data handling.

    Example:

    // Java program to demonstrate DataInputStream
    import java.io.*;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            DataInputStream reader = new DataInputStream(System.in);
    
            // Reading an integer input
            System.out.print("Enter an integer: ");
            int inputInt = Integer.parseInt(reader.readLine());
    
            // Reading a string input
            System.out.print("Enter a string: ");
            String inputString = reader.readLine();
    
            // Output the input values
            System.out.println("You entered integer: " + inputInt);
            System.out.println("You entered string: " + inputString);
        }
    }

    Input:

    Enter an integer: 30
    Enter a string: Java Programming

    Output:

    You entered integer: 30
    You entered string: Java Programming

    Java System.out.println()

    The System.out.println() method is used in Java to print a message or value to the console. It is a commonly used function that prints any argument passed to it, followed by a new line.

    Breakdown of System.out.println()

    The statement can be split into three parts for better understanding:

    1. System: This is a final class from the java.lang package.
    2. out: A static member of the System class, which is an instance of the PrintStream class.
    3. println(): A method of the PrintStream class that prints the passed argument and adds a newline character at the end. It’s an enhanced version of print() that automatically moves the cursor to the next line.

    Syntax:

    System.out.println(parameter);

    Example of System.out.println()

    Example : Basic Output

    public class Example {
        public static void main(String[] args) {
            System.out.println("Hello");
            System.out.println("World");
            System.out.println("Java Programming");
        }
    }

    Output:

    Hello
    World
    Java Programming
    Java’s Other Standard Streams
    • System.in: The standard input stream used for reading input from the keyboard.
    • System.err: The standard error stream used to display error messages.

    Example:

    System.err.print("This is an error message");
    Overloads of println() Method

    Java supports method overloading, allowing multiple methods with the same name but different parameter types. PrintStream provides various overloads of the println() method for different data types.

    Example of Overloaded println() Methods

    public class Example {
        public static void main(String[] args) {
            int num = 15;
            char letter = 'A';
            String text = "Java";
            double decimal = 25.78;
            float floatNum = 10.5f;
            boolean flag = true;
    
            // Various overloaded versions of println()
            System.out.println(); // Prints an empty line
            System.out.println(num);
            System.out.println(letter);
            System.out.println(text);
            System.out.println(decimal);
            System.out.println(floatNum);
            System.out.println(flag);
        }
    }

    Output:

    15
    A
    Java
    25.78
    10.5
    true
    Difference Between System.out.print() and System.out.println()
    • System.out.print(): Prints the provided text or value on the console and keeps the cursor on the same line.
    • System.out.println(): Prints the provided text or value, and moves the cursor to the next line.

    Example:

    public class Example {
        public static void main(String[] args) {
            System.out.println("Using print():");
    
            // Using print()
            System.out.print("Hello ");
            System.out.print("World ");
            System.out.print("Java");
    
            System.out.println(); // Moving to the next line
            System.out.println("Using println():");
    
            // Using println()
            System.out.println("Hello");
            System.out.println("World");
            System.out.println("Java");
        }
    }

    Output:

    Using print():
    Hello World Java
    Using println():
    Hello
    World
    Java

    Formatted Output in Java using printf()

    In programming, it is often necessary to display output in a specific format. Java’s printf() method, similar to the C language’s printf, allows formatting of output using format specifiers. In this article, we’ll cover different ways to format output in Java using printf().

    Formatting Using printf()

    printf() uses format specifiers to format different data types. The commonly used data types for formatting are:

    1. Number Formatting
    2. Decimal Number Formatting
    3. Boolean Formatting
    4. Character Formatting
    5. String Formatting
    6. Date and Time Formatting

    1. Number Formatting

    For formatting integers (like intlong), the format specifier used is %d.

    Example:

    public class Example {
        public static void main(String[] args) {
            int number = 10000;
    
            // Format with commas separating thousands
            System.out.printf("%,d%n", number);
        }
    }

    Output:

    10,000

    2. Decimal Number Formatting

    To format decimal numbers, we use the %f specifier.

    Example:

    public class Example {
        public static void main(String[] args) {
            double pi = 3.14159265359;
    
            // Formatting decimal numbers
            System.out.printf("%f\n", pi);
            System.out.printf("%5.3f\n", pi);
            System.out.printf("%5.2f\n", pi);
        }
    }

    Output:

    3.141593
    3.142
     3.14

    3. Boolean Formatting

    Boolean values can be formatted using %b or %B.

    Example:

    public class Example {
        public static void main(String[] args) {
            boolean boolTrue = true;
            boolean boolFalse = false;
            Integer nullValue = null;
    
            System.out.printf("%b\n", boolTrue);  // true
            System.out.printf("%B\n", boolTrue);  // TRUE
            System.out.printf("%b\n", boolFalse); // false
            System.out.printf("%B\n", nullValue); // FALSE
        }
    }

    Output:

    true
    TRUE
    false
    FALSE

    4. Character Formatting

    Character formatting is done using the %c or %C specifiers.

    Example:

    public class Example {
        public static void main(String[] args) {
            char character = 'a';
    
            System.out.printf("%c\n", character); // a
            System.out.printf("%C\n", character); // A (uppercase)
        }
    }

    Output:

    a
    A

    5. String Formatting

    Strings are formatted using %s or %S.

    Example:

    public class Example {
        public static void main(String[] args) {
            String text = "java programming";
    
            System.out.printf("%s\n", text);  // java programming
            System.out.printf("%S\n", text);  // JAVA PROGRAMMING
        }
    }

    Output:

    java programming
    JAVA PROGRAMMING

    6. Date and Time Formatting

    Formatting date and time is more complex and requires specific knowledge of format specifiers such as %tT%tH%tM, etc.

    Example:

    import java.util.Date;
    
    public class Example {
        public static void main(String[] args) {
            Date currentTime = new Date();
    
            System.out.printf("Current Time: %tT\n", currentTime); // Full time format
            System.out.printf("Hours: %tH  Minutes: %tM Seconds: %tS\n",
                              currentTime, currentTime, currentTime); // Individual components
    
            // Time with AM/PM, milliseconds, nanoseconds, and time zone
            System.out.printf("%1$tH:%1$tM:%1$tS %1$tp %1$tL %1$tN %1$tz%n", currentTime);
        }
    }

    Output:

    Current Time: 11:32:36
    Hours: 11  Minutes: 32  Seconds: 36
    11:32:36 am 198 198000000 +0000
    Other Methods for Formatting

    1. Decimal Formatting using DecimalFormat

    DecimalFormat can be used to format numbers with various patterns.

    Example:

    import java.text.DecimalFormat;
    
    public class DecimalFormatting {
        public static void main(String[] args) {
            double num = 123.4567;
    
            // Formatting without fraction part
            DecimalFormat df = new DecimalFormat("####");
            System.out.println("Without fraction part: " + df.format(num));
    
            // Formatting to 2 decimal places
            df = new DecimalFormat("#.##");
            System.out.println("Formatted to 2 decimal places: " + df.format(num));
    
            // Formatting with appended zeroes
            df = new DecimalFormat("#.000000");
            System.out.println("With appended zeroes: " + df.format(num));
    
            // Formatting with leading zeroes
            df = new DecimalFormat("00000.00");
            System.out.println("With leading zeroes: " + df.format(num));
    
            // Formatting currency
            double income = 23456.789;
            df = new DecimalFormat("$###,###.##");
            System.out.println("Formatted income: " + df.format(income));
        }
    }

    Output:

    Without fraction part: 123
    Formatted to 2 decimal places: 123.46
    With appended zeroes: 123.456700
    With leading zeroes: 00123.46
    Formatted income: $23,456.79

    2. Formatting Dates using SimpleDateFormat

    SimpleDateFormat allows date formatting based on custom patterns.

    Example:

    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DateFormatting {
        public static void main(String[] args) {
            // Formatting date to 'dd-MM-yyyy'
            SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
            String formattedDate = sdf.format(new Date());
            System.out.println("Formatted Date: " + formattedDate);
    
            // Parsing a string date
            String dateStr = "02/18/1995";
            sdf = new SimpleDateFormat("MM/dd/yyyy");
            try {
                Date date = sdf.parse(dateStr);
                System.out.println("Parsed Date: " + date);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    Output:

    Formatted Date: 03-10-2024
    Parsed Date: Sat Feb 18 00:00:00 UTC 1995

    3. Static Variables

    Static variables are shared across all instances of a class and are declared with the static keyword. Only one copy of a static variable exists, regardless of how many objects are created.

    // Example of static variables
    public class StaticVariableExample {
        public static String company = "TechCorp";  // Static variable
    
        public static void main(String[] args) {
            System.out.println("Company: " + StaticVariableExample.company);  // Accessing without object
        }
    }

    Output:

    Company: TechCorp

    Java Variables

    In Java, the scope of a variable refers to the region in the code where the variable is accessible. Java has lexical (static) scoping, meaning the scope of a variable is determined at compile time and is not dependent on the function call stack. The scope rules in Java can be broadly classified into three categories based on where the variables are declared.

    1. Member Variables (Class-Level Scope)
    2. Local Variables (Method-Level Scope)
    3. Block Variables (Loop or Block-Level Scope)

    1. Member Variables (Class-Level Scope)

    Member variables are declared inside a class but outside any method, constructor, or block. They can be accessed anywhere within the class and can have different access levels (e.g., publicprivateprotected, or default). Access to member variables outside the class depends on the access modifier used.

    Example:

    public class Test {
        // Member variables
        int a;                  // Default access modifier
        private String b;        // Private member variable
        char c;                  // Default access modifier
    
        void method1() {
            // Member variables can be accessed here
            System.out.println(a);
            System.out.println(b);
        }
    
        int method2() {
            return a;
        }
    }
    • Public: Accessible within the class, in subclasses, and outside the class.
    • Protected: Accessible within the class and in subclasses but not outside the package.
    • Default (no modifier): Accessible within the same package but not outside it.
    • Private: Only accessible within the class.

    2. Local Variables (Method-Level Scope)

    Local variables are declared inside a method or constructor and are only accessible within that method. They must be initialized before use, and their lifetime is limited to the method’s execution. Once the method finishes, local variables are destroyed.

    Example:

    public class Test {
        void method1() {
            // Local variable
            int x = 10;
            System.out.println(x);  // Accessible inside the method
        }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.method1();
        }
    }

    Local variables do not retain their values once the method completes, and they are recreated each time the method is invoked.

    3. Block Variables (Loop or Block-Level Scope)

    Variables declared inside a block (within curly braces {}) are only accessible within that block. Once the block is exited, these variables are out of scope and cannot be accessed. This applies to variables declared inside loops or conditionals.

    Example of Block-Level Scope:

    public class Test {
        public static void main(String[] args) {
            {
                int x = 10;  // x is only accessible inside this block
                System.out.println(x);
            }
    
            // Uncommenting the following line will cause an error
            // System.out.println(x);  // x is out of scope here
        }
    }
    Loop Variables (Block Scope)

    Variables declared inside a loop have scope limited to the loop. They cannot be accessed outside the loop.

    Example:

    class Test {
        public static void main(String[] args) {
            for (int x = 0; x < 4; x++) {
                System.out.println(x);  // x is accessible inside the loop
            }
    
            // Uncommenting the following line will result in an error
            // System.out.println(x);  // x is out of scope here
        }
    }

    If you need to access a loop variable outside the loop, declare it before the loop:

    class Test {
        public static void main(String[] args) {
            int x;
            for (x = 0; x < 4; x++) {
                System.out.println(x);
            }
            System.out.println(x);  // x is accessible outside the loop
        }
    }

    Output:

    0
    1
    2
    3
    4
    Loop Variable Scope with Overlapping Names

    In Java, you cannot declare two variables with the same name within the same scope. However, in languages like C++, it’s possible to have the same variable name in nested scopes, which is not allowed in Java.

    Incorrect Example in Java (compilation error):

    class Test {
        public static void main(String[] args) {
            int a = 5;
            for (int a = 0; a < 5; a++) {  // Error: Variable 'a' is already defined
                System.out.println(a);
            }
        }
    }

    Output:

    Error: variable 'a' is already defined
    Valid Example with Loop Variable Declaration After Loop

    To avoid such errors, you can declare a variable outside the loop and use it after the loop finishes.

    Example:

    class Test {
        public static void main(String[] args) {
            for (int i = 1; i <= 10; i++) {
                System.out.println(i);  // Loop variable i
            }
    
            int i = 20;  // Declare i after the loop
            System.out.println(i);  // Access new i outside the loop
        }
    }

    Output:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    20