Java OOP, in a Nutshell
Tags: java, programming, oop
Published: 2024-06-18 09:10:55

This blog is about the implementation and working of various object oriented programming concepts in Java. If you want a quick overview or recap, then this blog is for you!

Firstly, lets quickly understand the core concepts of OOP:

## Encapsulation
**Definition :** The action of enclosing something in or as if in a <u>capsule</u>. (*Oxford*)  
In programming, encapsulation ensures that the user has limited access to the application data, that too via methods defined inside the application.
Everytime, we encapsulate the data, we define **getters** and **setters** to retrieve and assign the values of variables that holds the data, respectively.  
The encapsulation of variables and methods in java is achieved with the help of access modifiers. Here are a few of them:  
- `public` : These methods can be accessed from anywhere, from any file.
- `private` : These methods can only be accessed from within the class. Useful for _encapsulation_ of data.
- `protected` : These methods can be accessed only from within the class and its subclasses. Applies only when **inheritence** in involved in the program

Now, lets create a simple program, step by step to understand encapsulation!

**Step-1:** Create a empty public class <u>_Person_</u>
```
public class Person {
    // code will go here
}
```
**Step-2:** Declare data members <u>_name_ </u>of type <u>_string_</u>, and <u>_age_ </u>of type <u>_int_ </u>. Make sure the access modifier of these data members should be `private` as we dont want users to access them directly.
```
public class Person {
    private String name; // name of the person
    private int age; // age of the person
}
```
**Step-3:** Now, we have variables to hold name and age of a person, but since user cant directly access them, we need to define methods to set the values of these variables. These methods are called _setters_.  


```
// setters

// sets the name of the person
public void setName(String name) { 
    this.name = name;
}

// sets the age of the person
public void setAge(int age) {
    this.age = age;
}
```
**Step-4:** After defining setters, we need to define some more methods which will help the users to retrieve the encapsulated data, these methods are called _getters_.  
```
// getters

// returns the name of the person
public String getName() { 
    return name;
}

// returns the age of the person
public int getAge() {
    return age;
}
```
You are all set! In just 4 steps, you have implemented encapsulation.

Now, you can create an object of this class in a driver class (Main class) and retrieve/assign the values to the variables without directly accessing them like this :
```
public class Main {
    public static void main(String[] args) {
        Person person = new Person(); // creating an instance

        // setting person's name and age
        person.setName("Robert");
        person.setAge("29");

        // getting person's name and age
        person.getName();
        person.getAge();
    }
}
```
## Inheritance
**Definition :** When a class (sub/child class) derives properties (data members) and behaviors (class methods) from another class (super/parent class), enabling code reuse and extension.
To inherit the properties and behaviors of a super class, we use `extends` keyword in java

Implementing inheritance with an example:

**Step-1** : Create a super class <u>_Animal_</u> with a method <u>_eat()_</u>, since every animal eats.
```
// base/super/parent class
class Animal {
    void eat() {
        System.out.println("This animal eats food.");
    }
}
```
**Step-2** : Create a sub class <u>_Dog_</u> inheriting <u>_Animal_</u> class with a method <u>_bark()_</u>, since dog is the animal that barks.
```
// Derived/sub/child class
class Dog extends Animal {
    void bark() {
        System.out.println("The dog barks.");
    }
}
```
**Step-3** : Create an instance of <u>_Dog_</u> class in driver class, and call the methods of both classes.
```
public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.eat();  // Inherited method
        myDog.bark(); // Method specific to Dog
    }
}
```
Congrats, you have successfully implemented simple Inheritance in Java! Similarly, we can implement <u>multilevel</u>, <u>hierarchical </u>and <u>hybrid</u> inheritance also.
> Note : Java doesn't explicitly support multiple inheritance, to implement it, we define abstract methods in interface/s.

## Polymorphism
The word polymorphism is derived from Greek and means "having multiple forms."
**Definition :** the ability of a method to operate on different types of objects, allowing for different behaviors based on the object's actual class.
We use method overloading and method overriding to achieve polymorphism in java. Here's the implementation:

**Step-1** : Take the above super class <u>_Animal_</u> and define a method <u>_makeSound()_</u> in it.
```
// base class
class Animal {
    void eat() {
        System.out.println("This animal eats food.");
    }

    void makeSound() { // Method to be overridden
        System.out.println("Some sound of the animal");
    }
}
```
**Step-2** : Write another <u>_makeSound()_</u> but, with parameter _sound_ of type _string_. (i.e., _makeSound( String sound )_). This is known as **Method Overloading**
```
// base class
class Animal {
    void makeSound() { // Method to be overridden
        System.out.println("Some sound");
    }
    void makeSound(String sound) { // Overloaded method
        System.out.println(sound);
    }
}
```
**Step-3** : Create a derived class <u>_Cat_</u>, inheriting <u>_Animal_</u>
```
// derived class
class Cat extends Animal {
    // method overriding goes here
}
```
**Step-4** : Rewrite the <u>_makeSound()_</u> method with cat specific sound. This is known as **Method Overriding** 
```
// Derived class
class Cat extends Animal {
    @Override // just a convention, not necessary to write
    void makeSound() { // Overriding method
        System.out.println("Meow");
    }
}
```
 **Step-5** : Create a _Cat_ object in driver class, and run the overridden and overloaded methods.
```
public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Cat(); // A Cat object of type Animal
        myAnimal.makeSound(); // Calls overridden method, outputs "Meow"
        myAnimal.makeSound("Growl"); // Calls overloaded method, outputs "Growl"
    }
}
```
We have implemented polymorphism in just 5 steps!

## Abstraction
**Definiton :** The process of hiding complex implementation details and exposing only the essential features of an object or system.

Abstraction in Java is achieved with the help of :
- **_Abstract Classes :_** A superclass that defines generalized structure without actual implmentation of methods. It is achiever with the help of `abstract` keyword, used as both, class and method specifier. It can have non-abstract methods as well.
- **_Interfaces :_** An interface is a class blueprint that only includes <u>static</u> and <u>final</u> methods and variables. It can only contains abstract methods

Here's a simple implementation of Abstraction in Java :
**Step-1** : Create an abstract superclass <u>_Vehicle_</u> with an abstract method <u>_startEngine()_</u>.
```
public abstract class Vehicle {
    public abstract void startEngine();
}
```
**Step-2** : Create its subclass <u>_Car_</u> and override the <u>_startEngine()_</u> method with car specific content.
```
public class Car extends Vehicle {
    @Override
    public void startEngine() {
        System.out.println("Car engine started");
    }
}
```
**Step-3** : Create an interface <u>_flyable_</u> with method <u>_fly()_</u>.
```
public interface Flyable {
    void fly();
}
```
**Step-4** : Create another subclass of <u>_Vehicle_</u> named <u>_Airplane_</u> that also implements the <u>_flyable_</u> interface.
```
public class Airplane extends Vehicle implements Flyable {
    // overriding of methods goes here
}
```
**Step-5** : Override <u>_startEngine()_</u> and <u>_fly()_</u> methods with airplane specific content.
```
public class Airplane extends Vehicle implements Flyable {
    @Override // overriding abstract class method
    public void startEngine() {
        System.out.println("Airplane engine started");
    }

    @Override // overriding interface method
    public void fly() {
        System.out.println("Airplane is flying");
    }
}
```
 **Step-6** : Create a _Car_ and a _Airplane_object in driver class, and run the overridden methods.
```
public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car();
        car.startEngine();  // Output: Car engine started

        Airplane airplane = new Airplane();
        airplane.startEngine();  // Output: Airplane engine started
        airplane.fly();  // Output: Airplane is flying
    }
}
```
We have successfully implemented Abstraction in Java with the help of Abstract classes and Interfaces!

In this blog, we have explored and successfully implemented all fundamental Object-Oriented Programming (OOP) concepts. From encapsulation and inheritance to polymorphism and abstraction, these concepts are key to building robust and maintainable software solutions. Thanks for reading..!