Object-Oriented Programming (OOP) Concepts in Java

Java is an object-oriented programming language that follows key OOP principles: Encapsulation, Inheritance, Polymorphism, and Abstraction.

1. Encapsulation

Encapsulation is the process of bundling data (variables) and methods that operate on the data into a single unit, i.e., a class. It helps achieve data hiding and keeps the internal workings of an object hidden from the outside world.

Data Hiding

Data hiding is achieved by declaring variables as private, making them inaccessible from outside the class. Access to these variables is provided through public setter and getter methods.


public class Employee {
    private String name; // Private variable

    // Setter
    public void setName(String name) {
        this.name = name;
    }

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

Constructors

Constructors are special methods used to initialize objects. A constructor is called when an object of a class is created.


public class Employee {
    private String name;

    // Constructor
    public Employee(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Employee emp = new Employee("John");
System.out.println(emp.getName()); // Output: John
      

The 'this' Keyword in Java

The this keyword is a reference variable in Java that refers to the current object instance. It can be used in various contexts to refer to the instance of the current class.

1. Referring to Current Class Instance Variables

When instance variables and method parameters have the same name, the this keyword is used to distinguish the instance variables from the parameters.


  class Employee {
      private String name;
  
      // Constructor
      public Employee(String name) {
          this.name = name; // 'this.name' refers to the instance variable
      }
  
      public String getName() {
          return this.name; // 'this.name' is referring to the instance variable
      }
  
      public static void main(String[] args) {
          Employee emp = new Employee("John");
          System.out.println(emp.getName()); // Output: John
      }
  }
        

In the example above, the this keyword helps to differentiate the instance variable name from the constructor parameter name.

2. Calling Current Class Methods

The this keyword can also be used to call other methods in the current class. It's not mandatory to use this to call methods, but it can be useful for clarity, especially when method names are the same as variables or when calling from a constructor.


  class Employee {
      private String name;
  
      // Constructor
      public Employee(String name) {
          this.name = name; // Initialize the name using the constructor parameter
          this.display();    // Call another method from the same class
      }
  
      // Method to display the name
      public void display() {
          System.out.println("Employee Name: " + this.name);
      }
  
      public static void main(String[] args) {
          Employee emp = new Employee("Alice");
      }
  }
        

In the example above, the this.display() call within the constructor invokes the display() method of the current class, which outputs the name of the employee.

3. Calling Constructors from One Another

In Java, you can call one constructor from another constructor within the same class using this. This is known as constructor chaining.


  class Employee {
      private String name;
      private int age;
  
      // Constructor with two parameters
      public Employee(String name, int age) {
          this.name = name;
          this.age = age;
      }
  
      // Constructor with one parameter, calling the two-parameter constructor
      public Employee(String name) {
          this(name, 30); // Calling the two-parameter constructor with a default age value
      }
  
      public void display() {
          System.out.println("Name: " + this.name + ", Age: " + this.age);
      }
  
      public static void main(String[] args) {
          Employee emp1 = new Employee("John", 25);
          emp1.display(); // Output: Name: John, Age: 25
  
          Employee emp2 = new Employee("Alice");
          emp2.display(); // Output: Name: Alice, Age: 30
      }
  }
        

In the example above, the constructor with one parameter calls the constructor with two parameters using this(name, 30), which demonstrates constructor chaining. This allows for the reuse of code within constructors.

2. Inheritance

Inheritance is a mechanism where one class acquires the properties and behaviors of another class. This helps in code reuse and hierarchical classification.

Types of Inheritance

Why Java Does Not Support Multiple Inheritance

Java does not support multiple inheritance (a class inheriting from more than one class) to avoid ambiguity. If two superclasses have the same method, the subclass won't know which one to inherit. This is resolved through interfaces.

Uses of the 'super' Keyword in Java

The super keyword in Java is used to refer to the immediate parent class of the current object. It can be used in various ways to interact with parent class members, including instance variables, methods, and constructors.

1. Referencing Parent Class Instance Variables

The super keyword can be used to access instance variables in the parent class, especially when there is a name conflict with the subclass instance variables.


class Animal {
    protected String name = "Animal";

    public void display() {
        System.out.println("Animal: " + name);
    }
}

class Dog extends Animal {
    private String name = "Dog";

    public void display() {
        System.out.println("Dog: " + name);
        System.out.println("Parent class name: " + super.name); // Access parent class variable
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.display();
    }
}
      

Output:


Dog: Dog
Parent class name: Animal
      

In this example, the super.name refers to the parent class's name variable, while the name in the Dog class refers to the subclass's own instance variable.

2. Calling Parent Class Methods

The super keyword is used to call methods from the parent class, especially when the method is overridden in the subclass.


class Animal {
    public void sound() {
        System.out.println("Animal makes sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
        super.sound(); // Call parent class method
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();
    }
}
      

Output:


Dog barks
Animal makes sound
      

In this example, the super.sound() calls the sound() method from the parent class Animal after executing the overridden method in the subclass Dog.

3. Calling Parent Class Constructor

The super keyword is used to call the constructor of the parent class. This is useful when the parent class constructor needs to be executed before the subclass constructor.


class Animal {
    public Animal(String name) {
        System.out.println("Animal's name: " + name);
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name); // Calling parent class constructor
        System.out.println("Dog's name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
    }
}
      

Output:


Animal's name: Buddy
Dog's name: Buddy
      

In this example, the super(name) call invokes the constructor of the Animal class before the subclass constructor executes.

Difference Between this and super

this and super both refer to objects in Java, but they serve different purposes:

this super
Refers to the current object instance of the class in which it is used. Refers to the immediate parent class of the object.
Used to access the current class's variables, methods, and constructors. Used to access the parent class's methods and variables, and to call parent class constructors.
Can be used to differentiate between method parameters and instance variables when they have the same name. Used to invoke overridden methods or constructors from the parent class.

3. Polymorphism

Polymorphism allows one object to take on many forms. It enables one interface to be used for a general class of actions.

Method Overloading

Method overloading is when multiple methods have the same name but different parameter lists. It is determined at compile time.


class MathOperation {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}

MathOperation operation = new MathOperation();
System.out.println(operation.add(2, 3));   // Output: 5
System.out.println(operation.add(2.5, 3.5)); // Output: 6.0
      

Method Overriding

Method overriding occurs when a subclass provides a specific implementation of a method already defined in its superclass. It is determined at runtime.


class Animal {
    public void sound() {
        System.out.println("Animal makes sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

Animal obj = new Dog();
obj.sound(); // Output: Dog barks
      

4. Abstraction

Abstraction is the process of hiding the implementation details and showing only the functionality to the user. Java provides abstraction through abstract classes and interfaces.

Abstract Class

An abstract class is a class that cannot be instantiated and may contain abstract methods (without implementation) as well as concrete methods (with implementation).


abstract class Animal {
    abstract void sound(); // Abstract method

    public void eat() { // Concrete method
        System.out.println("Eating...");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Barking...");
    }
}

Animal animal = new Dog();
animal.sound(); // Output: Barking...
      

Interface

An interface is a contract that defines a set of methods without providing implementations. A class that implements an interface must provide implementations for all of its methods.


interface Animal {
    void sound();
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Barking...");
    }
}

Animal animal = new Dog();
animal.sound(); // Output: Barking...
      

Difference Between Abstract Class and Interface

Abstract Class Interface
Can have both abstract and concrete methods. Can only have abstract methods (prior to Java 8, but can have default methods in Java 8+).
Can have constructors. Cannot have constructors.
Used for "is-a" relationship. Used for "can-do" relationship.

Marker Interface

A marker interface is an interface with no methods or fields. It is used to mark a class for a specific behavior.


interface Serializable {}

class Person implements Serializable {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
      

Access Modifiers in Java

In Java, access modifiers are used to control the visibility and accessibility of classes, methods, and variables. The four main access modifiers in Java are:

1. private Access Modifier

The private access modifier restricts access to members (variables and methods) within the same class only. It cannot be accessed from outside the class.


  class MyClass {
      private int number = 10;
  
      private void display() {
          System.out.println("Number: " + number);
      }
  
      public static void main(String[] args) {
          MyClass obj = new MyClass();
          // System.out.println(obj.number); // Error: number has private access
          // obj.display(); // Error: display() has private access
      }
  }
        

In this example, the variable number and method display() are private and cannot be accessed outside the class.

2. default (No Modifier)

If no access modifier is specified, it is considered the default access modifier. Members with default access can only be accessed within classes in the same package.


  class MyClass {
      int number = 10; // default access modifier
  
      void display() { // default access modifier
          System.out.println("Number: " + number);
      }
  }
  
  class AnotherClass {
      public static void main(String[] args) {
          MyClass obj = new MyClass();
          System.out.println(obj.number); // Accessible because it's in the same package
          obj.display(); // Accessible because it's in the same package
      }
  }
        

In this example, the variable number and method display() have default access and can be accessed by other classes in the same package.

3. protected Access Modifier

The protected access modifier allows access to members from the same package and subclasses (even if they are in a different package).


  class Animal {
      protected String sound = "Roar"; // protected variable
  
      protected void makeSound() { // protected method
          System.out.println("Sound: " + sound);
      }
  }
  
  class Dog extends Animal {
      public static void main(String[] args) {
          Dog dog = new Dog();
          System.out.println(dog.sound); // Accessible because Dog is a subclass of Animal
          dog.makeSound(); // Accessible because Dog is a subclass of Animal
      }
  }
        

In this example, the sound variable and makeSound() method are protected and can be accessed within the subclass Dog, even though it is in a different class.

4. public Access Modifier

The public access modifier allows access to members from anywhere, regardless of package or class.


  class MyClass {
      public int number = 10; // public variable
  
      public void display() { // public method
          System.out.println("Number: " + number);
      }
  }
  
  class AnotherClass {
      public static void main(String[] args) {
          MyClass obj = new MyClass();
          System.out.println(obj.number); // Accessible because it's public
          obj.display(); // Accessible because it's public
      }
  }
        

In this example, the variable number and method display() are public and can be accessed from any class, even if it's in a different package.

Summary of Access Modifiers

Access Modifier Access Level Can Be Accessed By
private Members are accessible only within the same class. Only the class itself.
default Members are accessible within the same package. Classes in the same package.
protected Members are accessible within the same package and subclasses. Classes in the same package and subclasses (even if they are in different packages).
public Members are accessible from anywhere. Any class, regardless of package.