Object-Oriented Programming (OOP) Concepts in Java: Classes, Inheritance, Polymorphism, Encapsulation, and Abstraction

@Harsh
7 min readOct 5, 2024

--

As I dive deeper into Object-Oriented Programming (OOP) in Java, I’ve come to understand the significance of principles like classes, inheritance, polymorphism, encapsulation, and abstraction. These are the foundational pillars of writing clean, modular, and scalable code. This blog will cover these key OOP concepts in Java, along with practical examples to showcase how each concept enhances the structure and efficiency of Java applications.

1. What is a Class and Why Do We Need It?

A class in Java is essentially a blueprint for creating objects. It defines a set of properties (fields) and behaviors (methods) that the objects created from the class will possess. For example, if you create a class for a Car, you can define attributes like the car’s color, engine type, and behaviors like start, stop, and accelerate.

1. What is a Class and Why Do We Need It?

A class in Java is essentially a blueprint for creating objects. It defines a set of properties (fields) and behaviors (methods) that the objects created from the class will possess. For example, if you create a class for a Car, you can define attributes like the car’s color, engine type, and behaviors like start, stop, and accelerate.

Why Use Classes?

  • Modularity: Classes encapsulate related data and behaviors in one unit.
  • Reusability: Once defined, a class can be used to create multiple objects.
  • Maintainability: Code is easier to manage when divided into separate classes for each entity.

Here’s an example of a class in Java:

class Car {
String color;
int doors;

void start() {
System.out.println("Car is starting...");
}

void stop() {
System.out.println("Car is stopping...");
}
}

2. What is Instantiation (Objects)?

Once a class is defined, we can create objects from it. This process is called instantiation. In Java, instantiation is done using the new keyword. When we create an object, Java allocates memory for it and calls the class's constructor to initialize its properties.

Car myCar = new Car();

Here, myCar is an instance of the Car class.

Why Use the new Keyword for Object Creation?

The new keyword in Java is used to create new objects dynamically. When we use the new keyword:

  • Memory is allocated in the heap for the new object.
  • The constructor of the class is called to initialize the object’s fields.

3. Getter and Setter Methods: Accessor and Mutator

In OOP, encapsulation refers to bundling data (fields) and methods that operate on that data into a single unit (the class) while hiding the internal details from the outside world. This is where getter and setter methods, also known as accessor and mutator methods, come into play.

For example:

class Car {
private String color;

// Accessor (Getter)
public String getColor() {
return color;
}

// Mutator (Setter)
public void setColor(String color) {
this.color = color;
}
}

4. Inheritance in Java: Reusability of Code

Inheritance is one of the cornerstones of OOP. It allows a new class (child class) to inherit the properties and behaviors of an existing class (parent class). The primary goal of inheritance is code reusability — it helps avoid duplication by allowing a child class to use and extend the functionality of a parent class.

Types of Inheritance in Java

  1. Single Inheritance: One class inherits from another class.
class Vehicle {
// common properties and methods
}

class Car extends Vehicle {
// Car inherits Vehicle properties and methods
}

2. Multi-Level Inheritance: A class is derived from another class, which is also derived from another class.

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

3. Hierarchical Inheritance: Multiple classes inherit from the same parent class.

class Shape {}
class Circle extends Shape {}
class Square extends Shape {}

Why We Need Inheritance?

  • Code Reusability: By inheriting from an existing class, a child class automatically gains access to the parent class’s properties and methods.
  • Maintainability: Fixes and updates can be made in the parent class, and all child classes benefit automatically.
  • Extensibility: You can add new functionalities to existing code with minimal changes.

Multiple Inheritance in Java

Java does not support multiple inheritance (inheriting from more than one class) directly because it can lead to the diamond problem, where the same method is inherited from two or more parent classes, leading to ambiguity. However, interfaces are used in Java to simulate multiple inheritance.

5. Constructors and Constructor Overloading

A constructor is a special method that is called when an object is instantiated. It is used to initialize the object’s properties. Unlike regular methods, constructors have no return type and have the same name as the class.

class Car {
String color;

// Constructor
Car(String color) {
this.color = color;
}
}

Constructor Overloading

Constructor overloading allows multiple constructors in a class with the same name but different parameter lists, providing flexibility in how objects are created.

class Car {
String color;
int doors;

// Overloaded constructors
Car(String color) {
this.color = color;
}

Car(String color, int doors) {
this.color = color;
this.doors = doors;
}
}

6. Access Modifiers in Java: Private, Public, and Static

Access modifiers control the visibility of classes, methods, and variables in Java. The two most common modifiers are:

  • private: Accessible only within the class via methods only.
  • public: Accessible from any class without the need of methods.

For example:

class Car {
private String color; // only accessible within Car class
public int doors; // accessible from any class
}

Static Keyword

A static variable or method belongs to the class rather than an instance of the class. It means that the static field or method is shared across all instances. It is also known as Shared Variable. If any changes made to this member from any instance then it would affect all the instances.

class Car {
static int numberOfCars;
}

7. Method Overloading vs. Method Overriding: Understanding Polymorphism

Polymorphism in Java means “many forms,” and it allows methods to behave differently based on the context in which they are used. Polymorphism is divided into two types: compile-time polymorphism (method overloading) and run-time polymorphism (method overriding).

Method Overloading (Compile-Time Polymorphism)

Method overloading occurs when multiple methods with the same name but different parameter lists are defined in a class. The correct method to invoke is determined at compile time based on the method signature.

Example:

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

int add(int a, int b, int c) {
return a + b + c;
}
}

In the above example, the add method is overloaded with two versions: one that takes two parameters and another that takes three. This is compile-time polymorphism because the method is resolved at compile time based on the method signature.

Method Overriding (Run-Time Polymorphism)

Method overriding occurs when a child class provides a specific implementation of a method that is already defined in its parent class. In this case, the version of the method that is invoked is determined at run time, depending on the object type.

Example:

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

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

In the above example, the sound method is overridden in the Dog class. The version of sound that will be called depends on the actual object type at runtime. This is called run-time polymorphism because the method to be executed is determined at runtime.

Difference Between Method Overloading and Method Overriding

  • Method Overloading: Happens at compile time (determined by method signature). It allows multiple methods in a class with the same name but different parameters.
  • Method Overriding: Happens at runtime (based on object type). It occurs when a subclass provides its own implementation of a method defined in its parent class.

8. Dynamic Method Dispatch

Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than at compile time. It is essential for achieving runtime polymorphism in Java. When an overridden method is called using a reference variable of the parent class, Java determines which version of the method to execute based on the actual object being referenced.

Animal animal = new Dog(); // Animal reference, Dog object
animal.sound(); // Dog's sound method is called at runtime

In this example, although animal is a reference to the Animal class, the actual object is of type Dog. Therefore, when animal.sound() is called, the Dog class’s sound method is invoked.

9. Abstraction in Java

Abstraction is the process of hiding the implementation details and exposing only the necessary functionality. In Java, abstraction is achieved through abstract classes and interfaces.

Why Use Abstraction?

Abstraction allows developers to focus on the what rather than the how. It provides a clear separation between the interface (what methods a class should have) and the implementation (how those methods work).

Abstract Class Example:

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

void sleep() {
System.out.println("Animal is sleeping.");
}
}

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

In this example, the Animal class provides an abstract sound method, and it is up to each subclass (e.g., Dog) to provide its own implementation. Abstract classes help in defining a template for other classes.

Use Cases for Abstraction:

  • Building frameworks where certain methods must be implemented differently based on the specific application.
  • Reducing complexity by separating concerns — one class can focus on core behaviors while other classes provide specific details.

Conclusion

Learning Object-Oriented Programming (OOP) in Java has provided me with insights into how to design software that is modular, reusable, and maintainable. Concepts like classes, inheritance, polymorphism, encapsulation, and abstraction are essential for building real-world applications in an efficient and organized manner.

  • Classes provide a blueprint for objects.
  • Inheritance promotes code reuse.
  • Polymorphism adds flexibility to method behavior.
  • Encapsulation ensures data security.
  • Abstraction simplifies complex systems by hiding unnecessary details.

Through these principles, OOP provides the foundation to build large-scale systems that are both easy to maintain and extend. With each concept offering its unique benefits, mastering them has been a rewarding experience as I continue my Java journey!

--

--