PROGRAMMING PRINCIPLES/OOP

Core Java OOP Concepts : Simplified

This article covers the OOP concepts used for object-oriented design

Pravinkumar Singh
Javarevisited
Published in
6 min readJun 8, 2023

--

image by Pravinkumar Singh

Object-Oriented Programming (OOP) is a programming paradigm that revolves around the concept of objects, which represent real-world entities and their interactions. Java is an object-oriented programming language that enables developers to create modular, reusable, and scalable applications. In this article, we’ll explore all seven fundamental OOP concepts in Java: Encapsulation, Inheritance, Polymorphism, Abstraction, Association, Aggregation, and Composition along with code examples to illustrate their applications.

1. Encapsulation

Encapsulation is the process of bundling data (attributes) and methods (functions) that operate on the data within a single unit, known as a class. It helps protect an object’s internal state by restricting direct access to its attributes and providing controlled access through public methods.

Example

Let’s create a simple `BankAccount` class to demonstrate encapsulation:

public class BankAccount {
private String accountNumber;
private double balance;

public BankAccount(String accountNumber) {
this.accountNumber = accountNumber;
this.balance = 0;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
return true;
}
return false;
}
public double getBalance() {
return balance;
}
}

In this example, we encapsulate the accountNumber and balance attributes by making them private. We provide public methods deposit(), withdraw(), and getBalance() to interact with these attributes in a controlled manner.

2. Inheritance

Inheritance is the mechanism by which one class can inherit properties (attributes) and methods from another class. This allows developers to create new classes based on existing ones, promoting code reusability and reducing redundancy.

Example

Let’s create a simple class hierarchy to demonstrate inheritance:

public class Vehicle {
private String make;
private String model;
private int year;

public Vehicle(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
public void start() {
System.out.println("Starting the vehicle...");
}
// Getters and setters
}
public class Car extends Vehicle {
private int numberOfDoors;
public Car(String make, String model, int year, int numberOfDoors) {
super(make, model, year);
this.numberOfDoors = numberOfDoors;
}
@Override
public void start() {
System.out.println("Starting the car...");
}
// Getters and setters
}

In this example, we create a Vehicle class and a Car class that inherits from Vehicle. The Car class inherits the attributes and methods from Vehicle and can also define its own, like numberOfDoors. We also override the start() method in the Car class to provide a custom implementation.

3. Polymorphism

Polymorphism is the ability of an object to take on different forms. In Java, polymorphism allows objects of different classes to be treated as objects of a common superclass. This enables developers to write more flexible and extensible code.

Example

Let’s extend our `Vehicle` class hierarchy to demonstrate polymorphism:

public class Motorcycle extends Vehicle {
private boolean hasSidecar;

public Motorcycle(String make, String model, int year, boolean hasSidecar) {
super(make, model, year);
this.hasSidecar = hasSidecar;
}
@Override
public void start() {
System.out.println("Starting the motorcycle...");
}
// Getters and setters
}
public class PolymorphismDemo {
public static void main(String[] args) {
Vehicle car = new Car("Toyota", "Camry", 2021, 4);
Vehicle motorcycle = new Motorcycle("Harley-Davidson", "Sportster", 2021, false);
startVehicle(car);
startVehicle(motorcycle);
}
public static void startVehicle(Vehicle vehicle) {
vehicle.start();
}
}

In this example, we create a Motorcycle class that also inherits from Vehicle. In the PolymorphismDemo class, we create instances of Car and Motorcycle and pass them to the startVehicle() method, which accepts a Vehicle parameter. This demonstrates polymorphism, as the startVehicle() method can work with any object that is a subclass of Vehicle.

4. Abstraction

Abstraction is the process of hiding the complexity of a system and exposing only the essential features to the user. In Java, abstraction can be achieved using abstract classes and interfaces. Abstract classes are classes that cannot be instantiated and can contain abstract methods (methods without a body). Interfaces define a contract for implementing classes, specifying a set of methods that must be implemented.

Example

Let’s create an abstract Animal class and an interface Swimmable to demonstrate abstraction:

public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void makeSound();
// Getters and setters
}
public interface Swimmable {
void swim();
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Dolphin extends Animal implements Swimmable {
public Dolphin(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Click!");
}
@Override
public void swim() {
System.out.println("The dolphin is swimming...");
}
}

In this example, we create an abstract Animal class with an abstract method makeSound(). We also create a Swimmable interface with a swim() method. The Dog class extends Animal and provides an implementation for makeSound(). The Dolphin class extends Animal and implements the Swimmable interface, providing implementations for both makeSound() and swim() methods.

5. Association

Association is another fundamental principle of OOP that describes the relationship between classes. It represents a general “has-a” or “uses-a” relationship between objects, allowing them to communicate. Association can be unidirectional (one-way) or bidirectional (two-way), depending on whether the relationship is defined in one or both classes.

Let’s explore the association principle with an example.

Example

Consider a library system with Author and Book classes:

public class Author {
private String name;
public Author(String name) {
this.name = name;
}
// Getters and setters
}
public class Book {
private String title;
private Author author;
public Book(String title, Author author) {
this.title = title;
this.author = author;
}
// Getters and setters
}

In this example, a Book has an Author object, representing a unidirectional association relationship. The Book class has a reference to the Author class, indicating that a book has an author. However, the Author class does not have any reference to the Book class, so the relationship is one-way.

If we want to make the relationship bidirectional, we can modify the Author class to include a list of Book objects:

public class Author {
private String name;
private List<Book> books;
public Author(String name) {
this.name = name;
this.books = new ArrayList<>();
}
public void addBook(Book book) {
books.add(book);
}
// Getters and setters
}

Now, the Author class has a list of Book objects, and the relationship between the Author and Book classes is bidirectional. Both classes have references to each other, allowing objects of one class to communicate with objects of the other class.

6. Aggregation

Aggregation is a weak form of association that represents a “has-a” or “part-of” relationship between objects. In aggregation, the composed object (whole) can exist independently of its parts, and the whole does not manage the lifetime of the parts. If the whole is destroyed, the parts can still exist.

Example

Consider a university system with Department and Professor classes:

public class Professor {
private String name;
public Professor(String name) {
this.name = name;
}
// Getters and setters
}
public class Department {
private String name;
private List<Professor> professors;
public Department(String name) {
this.name = name;
this.professors = new ArrayList<>();
}
public void addProfessor(Professor professor) {
professors.add(professor);
}
// Getters and setters
}

In this example, a Department has a list of Professor objects, representing an aggregation relationship. The Department and Professor objects can exist independently of each other. If a Department is destroyed, the Professor objects can still exist and be associated with other departments.

7. Composition

A composition is an intense form of association that represents a “has-a” or “part-of” relationship between objects, similar to aggregation. However, in composition, the composed object (entire) is responsible for managing the lifetime of its parts. If the entire is destroyed, its parts are also destroyed.

Example

Consider a computer system with Computer and Processor classes:

public class Processor {
private String model;
public Processor(String model) {
this.model = model;
}
// Getters and setters
}
public class Computer {
private String name;
private Processor processor;
public Computer(String name, String processorModel) {
this.name = name;
this.processor = new Processor(processorModel);
}
// Getters and setters

In this example, a Computer has a Processor object, representing a composition relationship. The Computer object is responsible for creating and managing the lifetime of its Processor. If a Computer is destroyed, its Processor object is also destroyed, as it cannot exist independently.

Conclusion

OOP concepts in Java, such as Encapsulation, Inheritance, Polymorphism, and Abstraction, are fundamental principles that help developers create modular, reusable, and scalable applications.

Association, Aggregation, and Composition are three essential principles of OOP that help define relationships between classes. While the two of them represent “has-a” or “part-of” relationships, they differ in how the lifetime of the parts is managed. Aggregation allows parts to exist independently of the whole, while composition implies that the whole is responsible for managing the lifetime of its parts. The association helps to define relationships between classes and allows objects to communicate with each other. Understanding these relationships can help you design more robust and flexible software systems.

Peace!

Related reads: Programming Principles

--

--