As we make use of different objects in our daily lives, object-oriented programming also uses virtual objects or assets in a certain way to organise and write code. This is done primarily to handle complex problems, which can resemble real-life applications or gaming. Therefore, object-oriented programming breaks down the problem statement into objects and makes them valuable assets to reuse and duplicate certain code blocks to reduce complexity.
In this blog, we will see a detailed view of what is object-oriented programming and also we will explain the key concepts of OOP, like classes, objects, inheritance, and polymorphism. Furthermore, advantages and limitations of OOP are also mentioned along with how it is used in real-world applications.
What is OOP (Object Oriented Programming)?
A method that uses primarily objects to represent data and methods in a certain software code. These objects are instances of classes that also behave as blueprints or hardcoded assets that would be helpful in further coding for that particular software. Suppose you want to make a game that consists of many different features like avatars, weapons, character outfits, levels, missions, etc. Then, all these features can be taken as objects whose code can be reused, abstracted, and made static. Hence, an object can be inside a class that will define a property (data) and certain behaviours (methods) associated with that object.
Here, the objects can interact with each other to perform certain tasks. Moreover, this interaction will be based on principles like encapsulation, inheritance, abstraction, and polymorphism. In encapsulation, the core state of the objects is hidden or encapsulated to preserve certain default properties and expose only the necessary parts. On the other hand, inheritance allows new classes to directly or indirectly use the properties of the existing ones, making different inherited subclasses. Abstraction is used to save the programmer’s efforts in understanding complex code, as it hides a code block that is already programmed and does not need any modification. Finally, polymorphism makes objects reusable and makes it easy to handle different types of objects in a unified way.
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure
Key Concepts of OOP
As we have discussed, any object formation will require certain key concepts and a particular approach to avoid errors and complex code. These concepts include some constructor and destructor methods, classes, data abstraction, encapsulation, inheritance, polymorphism, coupling, cohesion, association, aggregation, composition, modularity, dynamic binding, and message passing. Therefore it is necessary to understand all these concepts in depth for developing proper object-oriented programming skills.
Class
You can represent the class as a blueprint, which is used to create objects in a predefined manner. Also, it must have attributes and methods to completely define any object. Here, you can see a more detailed explanation through an example.
Attributes (Fields): Variables that hold data specific to an object.
Methods: Functions that define the behaviours of an object.
Example:
Let’s consider a ‘Person’ class that has attributes like ‘name’ and ‘age’ and methods to display these attributes.
// Define the Person class
public class Person {
// Attributes (fields)
String name;
int age;
// Method to display person's details
void display() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}
}
Object
Objects can be created using the class blueprint as it is an instance of a class.
Instantiation: Creating an instance of a class using the new keyword.
Accessing Attributes and Methods: Using the dot (.) operator to access attributes and methods of the object.
Example:
Consider a ‘Dog’ class with attributes like ‘breed’ and ‘size’, and methods to bark and display these attributes.
The Dog class includes a constructor to initialise the breed and size attributes.
It has methods bark() and display() to simulate barking and display the dog’s details.
In the Main class, an object dog1 is created using the constructor, and its methods are called to simulate barking and display its details.
// Define the Dog class
public class Dog {
// Attributes (fields)
String breed;
String size;
// Constructor
public Dog(String breed, String size) {
this.breed = breed;
this.size = size;
}
// Method to simulate barking
void bark() {
System.out.println("Woof! Woof!");
}
// Method to display dog's details
void display() {
System.out.println("Breed: " + breed);
System.out.println("Size: " + size);
}
}
// Main class to test the Dog class
public class Main {
public static void main(String[] args) {
// Create an object of the Dog class using the constructor
Dog dog1 = new Dog("Labrador", "Large");
// Call methods
dog1.bark();
dog1.display();
}
}
Output:
Woof! Woof!
Breed: Labrador
Size: Large
Constructors and methods
Constructors: Constructors can be very helpful in initialising objects during their creation. Constructors can act as a particular default start to any function, as it can have default attributes and values to be assigned. They also have the same name as the class and do not have any return type.
Methods: Methods are functions defined within a class to perform actions on the objects.
Example:
Let’s take an example of a ‘Book’ class with attributes like ‘title’ and ‘author’, and methods to display these attributes.
The Book class has a constructor to initialise the title and author attributes.
It also has a method display() to print these attributes.
In the Main class, we create an object book1 of the Book class using the constructor and call the display() method to print the book’s details.
// Define the Book class
public class Book {
// Attributes (fields)
String title;
String author;
// Constructor to initialise attributes
public Book(String title, String author) {
this.title = title;
this.author = author;
}
// Method to display book's details
void display() {
System.out.println("Title: " + title);
System.out.println("Author: " + author);
}
}
// Main class to test the Book class
public class Main {
public static void main(String[] args) {
// Create an object of the Book class using the constructor
Book book1 = new Book("1984", "George Orwell");
// Call method to display book's details
book1.display();
}
}
Output:
Title: 1984
Author: George Orwell
Encapsulation
Sometimes wrapping data or attributes and methods into a single unit or a single code block would solve many different types of errors and security issues that might happen in programming future. Hence encapsulation is the practice of controlling access to the data and methods by using access specifiers. Encapsulation ensures the internal representation of an object that is hidden from the outside.
Here are the access specifiers which you can use in Java.
Private: The member is accessible only within the same class.
Protected: The member is accessible within the same class, subclasses, and within the same package.
Public: The member is accessible from any other class.
Default: The member is accessible only within the same package (no keyword used).
Example:
Let’s consider a Student class that demonstrates encapsulation using different access specifiers.
The Student class encapsulates the attributes ‘name’ and ‘age’ by making them private. It provides public methods to get and set these attributes.
Public methods ‘getName()’, ‘setName()’, ‘getAge()’, and ‘setAge()’ are provided to access and modify these attributes safely.
// Define the Student class
public class Student {
// Private attributes (fields)
private String name;
private int age;
// Public method to get the name
public String getName() {
return name;
}
// Public method to set the name
public void setName(String name) {
this.name = name;
}
// Public method to get the age
public int getAge() {
return age;
}
// Public method to set the age
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
System.out.println("Age must be positive.");
}
}
}
// Main class to test the Student class
public class Main {
public static void main(String[] args) {
// Create an object of the Student class
Student student1 = new Student();
// Set attributes using public methods
student1.setName("Alice");
student1.setAge(20);
// Get attributes using public methods
System.out.println("Student Name: " + student1.getName());
System.out.println("Student Age: " + student1.getAge());
}
}
Output:
Student Name: Alice
Student Age: 20
Inheritance
Inheritance is a fundamental concept in object-oriented programming that allows a class to inherit properties and methods from another class. This promotes code reusability and establishes a relationship between classes.
There are several types of inheritance:
Single Inheritance: A class inherits from one superclass.
Multilevel Inheritance: A class inherits from a superclass, and another class inherits from that subclass.
Hierarchical Inheritance: Multiple classes inherit from a single superclass.
Multiple Inheritance: A class inherits from more than one superclass (not supported directly in Java, but can be achieved using interfaces).
Hybrid Inheritance: A combination of two or more types of inheritance (not supported directly in Java).
Example: Single Inheritance
Single inheritance occurs when a class inherits from one superclass. Let’s consider an example where ‘Car’ inherits from ‘Vehicle’.
Here, the ‘Vehicle’ class defines common attributes and methods for vehicles. The ‘Car’ class extends ‘Vehicle’ and inherits its properties and methods.
// Define the Vehicle class
class Vehicle {
// Attributes (fields)
String brand;
int speed;
// Method to display vehicle details
public void display() {
System.out.println("Brand: " + brand);
System.out.println("Speed: " + speed);
}
}
// Define the Car class that extends Vehicle
class Car extends Vehicle {
// Additional attribute specific to Car
int doors;
// Method to display car details
public void displayCarDetails() {
display(); // Call the inherited method
System.out.println("Doors: " + doors);
}
}
// Main class to test the Vehicle and Car classes
public class Main {
public static void main(String[] args) {
// Create an object of the Car class
Car myCar = new Car();
// Set attributes
myCar.brand = "Toyota";
myCar.speed = 120;
myCar.doors = 4;
// Call method to display car details
myCar.displayCarDetails();
}
}
Output:
Brand: Toyota
Speed: 120
Doors: 4
Multilevel Inheritance: Multiple inheritance can involve a chain of various inheritances in the same program. For example, let us take a vehicle, then its type can be defined as Vehicle -> Car -> ElectricCar. Thus, the ‘ElectricCar’ class inherits from ‘Car’, which in turn inherits from ‘Vehicle’.
class ElectricCar extends Car {
// Additional attribute specific to ElectricCar
int batteryCapacity;
// Method to display electric car details
public void displayElectricCarDetails() {
displayCarDetails(); // Call the inherited method from Car
System.out.println("Battery Capacity: " + batteryCapacity + " kWh");
}
}
Hierarchical Inheritance: This type occurs when multiple classes inherit from the same superclass. For instance, ‘Car’ and ‘Bike’ can both inherit from ‘Vehicle’.
class Bike extends Vehicle {
// Additional attribute specific to Bike
boolean hasCarrier;
// Method to display bike details
public void displayBikeDetails() {
display(); // Call the inherited method from Vehicle
System.out.println("Has Carrier: " + hasCarrier);
}
}
Multiple Inheritance: Java does not support multiple inheritance directly to avoid complexity and ambiguity. However, it can be achieved using interfaces. For example, a class FlyingCar could implement both Car and Flyable interfaces.
interface Flyable {
void fly();
}
// Define the FlyingCar class that extends Car and implements Flyable
class FlyingCar extends Car implements Flyable {
// Implement the fly method
public void fly() {
System.out.println("The car is flying.");
}
}
Hybrid Inheritance: A combination of multiple types of inheritance, which is also not directly supported in Java due to the complexity it can introduce. This can be managed using interfaces.
Abstraction
Abstraction works behind the scenes and working on certain parts of a software program, which could be hectic if not hidden in a proper way. For example, if you have a perfect code for defining a method or object, then you must hide that code block to reduce the complexity of the whole program and focus just on what the object does rather than how it does.
Abstract Class: A class that cannot be instantiated and is designed to be subclassed. It may contain abstract methods (methods without a body) that must be implemented by subclasses.
Abstract Method: A method that is declared without an implementation and must be overridden in a subclass.
Example:
Let’s take an example of an abstract class Shape that defines the common feature of different shapes. Subclasses like Circle and Rectangle will provide specific implementations.
The Shape class is an abstract class with an abstract method draw(). It declares the method but doesn’t implement it.
The Circle class extends Shape and provides an implementation for the draw() method.
The Main class creates an object of the Circle class and uses its methods.
// Define the abstract Shape class
abstract class Shape {
// Abstract method to draw the shape
public abstract void draw();
}
// Define the Circle class that extends Shape
class Circle extends Shape {
private double radius;
// Constructor to initialise the radius
public Circle(double radius) {
this.radius = radius;
}
// Implementation of the abstract method draw
@Override
public void draw() {
System.out.println("Drawing a circle with radius: " + radius);
}
}
// Main class to test the Shape and Circle classes
public class Main {
public static void main(String[] args) {
// Create an object of the Circle class
Circle myCircle = new Circle(5.0);
// Call the draw method
myCircle.draw();
}
}
Output:
Drawing a circle with radius: 5.0
Polymorphism
Another fundamental concept of OOP is polymorphism, where you can treat objects as instances of their parent class rather than their actual class. Hence it is essential for designing and flexibility of the code.
Compile-time Polymorphism (Method Overloading): This occurs when multiple methods in the same class have the same name but different parameters.
Runtime Polymorphism (Method Overriding): This occurs when a subclass provides a specific implementation of a method that is already defined in its superclass.
Example 1: Method Overloading
The ‘Calculator’ class demonstrates method overloading with two ‘add’ methods having different parameter lists.
In the Main class, we create an object of the ‘Calculator’ class and call the overloaded add methods.
// Define the Calculator class
public class Calculator {
// Method to add two integers
public int add(int a, int b) {
return a + b;
}
// Overloaded method to add three integers
public int add(int a, int b, int c) {
return a + b + c;
}
}
// Main class to test the Calculator class
public class Main {
public static void main(String[] args) {
// Create an object of the Calculator class
Calculator calc = new Calculator();
// Call the overloaded add methods
System.out.println("Sum of 2 and 3: " + calc.add(2, 3));
System.out.println("Sum of 2, 3, and 4: " + calc.add(2, 3, 4));
}
}
Output:
Sum of 2 and 3: 5
Sum of 2, 3, and 4: 9
Example 2: Method Overriding
The ‘Animal’ class has a method ‘makeSound()’.
The ‘Dog’ and ‘Cat’ classes override the ‘makeSound()’ method to provide specific implementations.
In the Main class, we create objects of the ‘Dog’ and ‘Cat’ classes and call the overridden ‘makeSound()’ method.
// Define the Animal class
class Animal {
// Method to make a sound
public void makeSound() {
System.out.println("Some generic animal sound");
}
}
// Define the Dog class that extends Animal
class Dog extends Animal {
// Overridden method to make a sound specific to dogs
@Override
public void makeSound() {
System.out.println("Bark");
}
}
// Define the Cat class that extends Animal
class Cat extends Animal {
// Overridden method to make a sound specific to cats
@Override
public void makeSound() {
System.out.println("Meow");
}
}
// Main class to test the Animal, Dog, and Cat classes
public class Main {
public static void main(String[] args) {
// Create objects of the Dog and Cat classes
Animal myDog = new Dog();
Animal myCat = new Cat();
// Call the overridden makeSound method
myDog.makeSound();
myCat.makeSound();
}
}
Output:
Bark
Meow
Coupling
Coupling refers to the degree of direct knowledge that one class has of another. Lower coupling is generally preferred as it reduces dependencies between classes, making the code easier to maintain and less prone to bugs.
Example:
The ‘Car’ class directly depends on the ‘Engine’ class, demonstrating high coupling. The ‘Car’ class creates an instance of ‘Engine’ and calls its methods.
// Define the Engine class
class Engine {
public void start() {
System.out.println("Engine started.");
}
}
// Define the Car class
class Car {
private Engine engine;
// Constructor
public Car() {
this.engine = new Engine(); // High coupling
}
public void startCar() {
engine.start();
System.out.println("Car started.");
}
}
// Main class to test Car and Engine classes
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.startCar();
}
}
Output:
Engine started.
Car started.
Cohesion
When we talk about the relationship between methods in a single class, we use cohesion to know how closely the responsibilities are related. If we find high cohesion, then it refers to a class with more understandable code that can be easy to maintain and in handling errors.
Example:
The ‘Printer’ class has high cohesion as it only contains methods related to printing, making it focused and maintainable.
// Define the Printer class
class Printer {
// High cohesion as it focuses only on printing tasks
public void printDocument(String document) {
System.out.println("Printing: " + document);
}
public void printPhoto(String photo) {
System.out.println("Printing photo: " + photo);
}
}
// Main class to test the Printer class
public class Main {
public static void main(String[] args) {
Printer myPrinter = new Printer();
myPrinter.printDocument("MyDocument.pdf");
myPrinter.printPhoto("MyPhoto.jpg");
}
}
The association represents a relationship between two classes. It defines how objects of one class are connected to objects of another.
Example:
The ‘Teacher’ and ‘Student’ classes are associated with each other through their objects, demonstrating an association relationship.
// Define the Teacher class
class Teacher {
String name;
public Teacher(String name) {
this.name = name;
}
public void teach() {
System.out.println(name + " is teaching.");
}
}
// Define the Student class
class Student {
String name;
public Student(String name) {
this.name = name;
}
public void attendClass() {
System.out.println(name + " is attending class.");
}
}
// Define the Main class to demonstrate an association
public class Main {
public static void main(String[] args) {
Teacher teacher = new Teacher("Mr. Smith");
Student student = new Student("John");
teacher.teach();
student.attendClass();
}
}
Output:
Mr. Smith is teaching.
John is attending class.
Aggregation
Another form of association is aggregation. Here one of the classes contains a reference to any other class and it would have a “has-a” relationship with a whole-part scenario.
Example:
The ‘Person’ class contains an ‘Address’ object, demonstrating aggregation. This shows a whole-part relationship.
// Define the Address class
class Address {
String city;
String state;
public Address(String city, String state) {
this.city = city;
this.state = state;
}
}
// Define the Person class
class Person {
String name;
Address address; // Aggregation (has-a relationship)
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public void display() {
System.out.println(name + " lives in " + address.city + ", " + address.state);
}
}
// Main class to test Person and Address classes
public class Main {
public static void main(String[] args) {
Address address = new Address("New York", "NY");
Person person = new Person("Alice", address);
person.display();
}
}
Output:
Alice lives in New York, NY
Composition
Composition is a stronger form of aggregation where the contained object cannot exist independently of the container object. It represents a “part-of” relationship.
Example:
The ‘Car’ class creates and manages the lifecycle of the ‘Engine’ class, demonstrating composition. The ‘Engine’ object cannot exist without the ‘Car’ object.
// Define the Engine class
class Engine {
public void start() {
System.out.println("Engine started.");
}
}
// Define the Car class
class Car {
private Engine engine; // Composition (part-of relationship)
public Car() {
this.engine = new Engine(); // Engine is part of Car
}
public void startCar() {
engine.start();
System.out.println("Car started.");
}
}
// Main class to test Car and Engine classes
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.startCar();
}
}
Output:
Engine started.
Car started.
Modularity
To further improve code manipulation with lesser complexity, modularity comes into action. It is the design principle that helps to divide a system into distinct modules that are interchangeable and each one of them holds a specific responsibility. Overall it improves the code maintainability and the code reusability.
Example:
The ‘MathOperations’ and ‘StringOperations’ classes, as mentioned below, are designed as separate modules. Both of them have a specific set of responsibilities. This modularity makes the code easier to manage and extend.
// Define the MathOperations class
class MathOperations {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
// Define the StringOperations class
class StringOperations {
public String concatenate(String a, String b) {
return a + b;
}
public int getLength(String str) {
return str.length();
}
}
// Main class to test modularity
public class Main {
public static void main(String[] args) {
MathOperations mathOps = new MathOperations();
StringOperations stringOps = new StringOperations();
System.out.println("Sum: " + mathOps.add(5, 3));
System.out.println("Concatenated String: " + stringOps.concatenate("Hello", " World"));
}
}
Output:
Sum: 8
Concatenated String: Hello World
Dynamic Binding
Dynamic Binding is the occurrence of a method being invoked and it is found to be at runtime instead of compile-time, hence it is also known as late binding. This helps us to make more flexible and extensible code as until the program is running, we do not know which exact method will be called. Dynamic binding is commonly used in conjunction with polymorphism.
Example:
In this example, we have an ‘Animal’ class with a method ‘makeSound()’, and two subclasses ‘Dog’ and ‘Cat’ that override this method. In the Main class, an ‘Animal’ reference is assigned to different objects (Dog and Cat) at runtime, demonstrating dynamic binding.
// Define the Animal class
class Animal {
// Method to make a sound
public void makeSound() {
System.out.println("Some generic animal sound");
}
}
// Define the Dog class that extends Animal
class Dog extends Animal {
// Overridden method to make a sound specific to dogs
@Override
public void makeSound() {
System.out.println("Bark");
}
}
// Define the Cat class that extends Animal
class Cat extends Animal {
// Overridden method to make a sound specific to cats
@Override
public void makeSound() {
System.out.println("Meow");
}
}
// Main class to test dynamic binding
public class Main {
public static void main(String[] args) {
Animal myAnimal;
// Dynamic binding in action
myAnimal = new Dog();
myAnimal.makeSound(); // Outputs "Bark"
myAnimal = new Cat();
myAnimal.makeSound(); // Outputs "Meow"
}
}
Output:
Bark
Meow
Message Passing
Internally, objects can communicate with each other by sending and receiving certain information or data attributes with the help of message passing. We adopt this concept for easy implementation of encapsulation and modularity. This concept is central to object-oriented programming, where objects interact by calling each other’s methods.
Example:
In the example below, we have defined a Sender class with a method ‘sendMessage()’ which takes a Receiver object and a message as parameters. In the Main class, an object of the Sender class sends a message to an object of the Receiver class, demonstrating message passing.
// Define the Sender class
class Sender {
// Method to send a message
public void sendMessage(Receiver receiver, String message) {
receiver.receiveMessage(message);
}
}
// Define the Receiver class
class Receiver {
// Method to receive a message
public void receiveMessage(String message) {
System.out.println("Message received: " + message);
}
}
// Main class to test message passing
public class Main {
public static void main(String[] args) {
Sender sender = new Sender();
Receiver receiver = new Receiver();
// Sender sends a message to Receiver
sender.sendMessage(receiver, "Hello, this is a message!");
}
}
Reusability: For the reusable purpose of code blocks, the classes can be taken in different programs or projects, saving development time.
Modularity: Here, the Programs can be divided further into smaller manageable sections.
Scalability: One of the main benefits we see in OOP systems is we can expand current OOP software projects with minimal impact on existing code.
Encapsulation: Data and methods are put together inside a secure and non-editable code, protecting the integrity of the data. With encapsulation, anyone could not have unauthorised access and modification.
Polymorphism: Allows objects to be treated as instances of their parent class, simplifying code and improving readability.
Inheritance: New classes can inherit properties and methods from existing classes, promoting code reuse and reducing redundancy.
Limitations of OOP
Despite its many advantages, OOP also has some limitations:
Complexity: OOP can introduce complexity in design and implementation, requiring careful planning and design.
Performance: OOP can sometimes lead to slower performance compared to procedural programming due to additional layers of abstraction and memory overhead.
Learning Curve: OOP concepts can be challenging for beginners to grasp, requiring an understanding of various principles.
Overhead: Managing objects and classes can add overhead to the program, resulting in larger codebases.
Applications of OOP
OOP is widely used in many areas of software development due to its versatility and powerful features:
Software Development: Today, we generally use OOP for the development of a wide range of software applications, including enterprise applications, desktop software, and web applications.
Game Development: OOP is ideal for designing complex game systems, allowing for the creation of interactive objects and facilitating modular and reusable code.
Graphical User Interfaces (GUIs): OOP is used to develop user-friendly interfaces, supporting event-driven programming and improving user interaction.
Real-Time Systems: OOP is used in real-time systems where objects can represent real-world entities and interactions, making the system easier to design and understand.
Conclusion
In conclusion, we certainly needed a solution to develop a certain approach towards software programming that is fixed, compiled with rules and regulations, along with a structure that can be studied by every programmer. Hence, object-oriented programming has given us this opportunity to perfectly program and transform the way we approach software development. Object-oriented programming always closely resembles the concepts of real-world problems and can be greatly inspired by the solutions presented to us to reduce complexity for upcoming software developments.
Despite its advantages, OOP also has limitations such as complexity and performance overhead. However, the benefits often outweigh the drawbacks, making OOP a preferred choice for many developers. Understanding the key concepts like classes, objects, inheritance, and polymorphism is essential for leveraging the full potential of OOP in your projects.
FAQs
What is OOP?
Object-oriented programming is a method of programming based on objects and classes.
What are the four main principles of OOP?
The four main principles are encapsulation, inheritance, polymorphism, and abstraction.
What is a class in OOP?
A class is a blueprint for creating objects and defining their properties and behaviours.
What is an object in OOP?
An object is an instance of a class containing data and methods.
What is inheritance in OOP?
Inheritance allows a class to inherit properties and methods from another class.
What is polymorphism in OOP?
Polymorphism allows objects to be treated as instances of their parent class.
What is encapsulation in OOP?
Encapsulation hides the internal state of an object and exposes only necessary parts.
What is an abstract class?
An abstract class cannot be instantiated and is designed to be subclassed.
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.