OOP Simplified!

Sadisha Nimsara
9 min readApr 4, 2024

--

Introduction

In this article, you will learn and understand Object-Oriented Programming (OOP) principles, which are essential for every software engineer and developer. I emphasize “understand” here because, many individuals, especially beginners, acquire knowledge of OOP principles from various sources without grasping how to apply them in programming. Therefore, I hope that this article will assist them in understanding OOP principles thoroughly.

Pre-requisites

This article explains OOP concepts using Java. To fully benefit from this content, I recommend that you possess a foundational understanding of programming fundamentals, including syntax, data types, variables, functions, and packages.

Enjoy your learning journey!

Why OOP?

OOP simplifies programs by translating real-world concepts into programming. It involves mapping real-world objects to objects within the program, making programs more manageable and comprehensible.

Classes & Objects

Understanding this concept is crucial. If you’ve seen Java code previously, you’ve likely noticed the keyword class. We can view a class as a blueprint for a real-world object. A class contains properties and behaviours. In OOP, we call them instance variables and methods.

For instance, just like a building plan (blueprint) contains the entire structure of a building, in OOP terms, we can consider the blueprint as the class and the building as an object. It’s important to note that multiple buildings can be constructed using the same plan (blueprint).

To create an object from a class, we call the constructor. A class can have multiple constructors. In cases where the developer doesn’t specify one, a default constructor will be used. To create an object, we invoke the constructor using the new keyword, followed by the class name and parentheses.

// defauled constructor
User user = new User();

// user defined constructor
// accepts name as a parameter
User user2 = new User("Sadisha Nimsara");

Interface

Interfaces in programming define the structure of a class, much like how a class serves as a blueprint for an object. Interfaces contain public static final variables and abstract methods. (An abstract method is a method that does not have a body.)

public interface User {
// all variables must be declared as public, static, and final
public static final String type;

// this is also public, static, and final by default.
// only in newer java version
String _type;

// interface method (does not have a body)
public void setType();
}

OOP Concepts

There are four OOP principles. These principles form the foundation of object-oriented design and programming principles.

  1. Inheritance
  2. Abstraction
  3. Encapsulation
  4. Polymorphism

Let’s discuss them one by one.

Inheritance

Inheritance refers to establishing connections between classes.

Extend or Implement?

In Java, we utilize the keywords extends and implements to accomplish inheritance. Inheritance can take various forms, including:

  • Inherit class from another class — use extends
  • Inherit class from an interface — use implements
  • Inherit interface from another interface — use extends

This process of inheritance establishes a relationship between a Parent class (superclass) and a Child class (subclass). Put simply, the parent class provides inheritance, while the child class receives it.

The way we can achieve properties and methods from parent class is using the super keyword. If you want to call the constructor of the super class, you may call super(); .

class User {
public String name;

public float calculateSalary() {
// calculate the salary
return 0;
}
}

class Employee extends User {
public int id;

public Enployee(int id) {
// calls the constructor of the super class
super();
}

public String getName() {
// get a property value from super class
return super.name;
}

public float getSalary() {
// calling a method from super class
return super.calculateSalary();
}
}

As you can see in this example, User class has one property called name and one method called calculateSalary(). Employee class is extended from the User class. So it gets inheritance from User class. Thats why we can access nameand calculateSalary() methods within the Employee class by calling super.name and super.calculateSalary() .

If you’re curious about how data is accessible to child classes, we can manage the access of inherited properties and methods using Access Modifiers. This concept is known as Encapsulation, and we’ll delve into it later in this article.

Inheritance Types

There are multiple inheritance types as you can see in this picture. But, Java does not support Multiple inheritance for classes by default. However, we can use interfaces to achieve multiple inheritance in java.

interface A { 
/* some methods */
}
interface B {
/* some methods */
}
public class C implements A, B{
/* overriden methods from A and B */
}

As you can see in the above example, Class C inherits all the properties and behaviours in Interface A and Interface B .

Abstraction

Abstraction stands as a fundamental principle in Object-Oriented Programming. It defines a model to create an application component. The implementation of abstraction depends on the language-specific features and processes.

In a nutshell, This means hiding the implementation and providing the functionality.

For instance, when you start your car, you simply insert the key and turn it or press the start button. The complex internal workings involved in starting the car are abstracted from you, where the implementation remains hidden, and only the functionality is visible.

There are two ways to achieve abstraction in java:

  • Abstract classes(0 to 100% abstraction)
  • Interfaces (100% abstraction)

Abstract classes

An abstract class is defined with the abstract keyword and can include data properties like a regular class. Additionally, it can contain both abstract and non-abstract methods. We cannot instantiate an object directly from an abstract class; instead, it must be extended by a child class.

abstract class User {
protected String name;
protected float age;
protected float salaryRate;

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

// abstract method
// does not have a method body
// must be overridn by a child class
public abstract void calculateSalary();
}
public class Employee extends User {

@Override
public void calculateSalary() {
// calculate the salary
}

}
public class Main {
public static void main(String[] args) {

User user = new User(); // not allowed (error)

Employee employee1 = new Employee(); // allowed

User employee2 = new Employee(); // allowed

}
}

Interfaces

An interface is an example of an abstract class that achieves 100% abstraction. But, if we use interfaces, data properties are common for all the clid classes. Because, interfaces have public static and final variables.

interface Vehicle {
public void start();
public void accelerate();
public void stop();
}
public class Car extends Vehicle {

@Override
public void start() {
// start a car
}

@Override
public void accelerate() {
// accelerate a car
}

@Override
public void stop() {
// stop a car
}

}
public class Main {
public static void main(String[] args) {

Vehicle vehicle = new Vehicle(); // not allowed (error)

Car car1 = new Car(); // allowed

Vehicle car2 = new Car(); // allowed

}
}

When to use Abstract classes and when to user Interfaces

Suppose A is an interface, and B and C are A’s subclasses.

If B and C share the same properties with identical values, it’s preferable to use interfaces.

However, if B and C have the same properties but different values for them, abstract classes would be a better choice.

Encapsulation

Encapsulation involves restricting access to data and methods within classes. Classes consist of variables and methods. We can manage how other classes access these variables and methods. This concept is known as encapsulation.

There are four access modifiers used to restrict access within a class:

  • Default (No keyword): Limits access to classes within the same package.
  • Private: Limits access to the class itself.
  • Protected: Limits access to the same class and its subclasses.
  • Public: Removes all access restrictions.

There is a misconception about this: “Encapsulation increases security.” However, that is not the case. Encapsulation is not directly related to security.

Default (No keyword)

public class User {
int id;
string name;
Date birthday;
float age;

void calculateAge() { /*calculates the age from birthday*/ }
}

All the variables and methods can be accessed within the package.

Private

public class User {
private int id;
private string name;
private Date birthday;
private float age;

void calculateAge() { /*calculates the age from birthday*/ }
}

In this example, all variables can be accessed within the class. However, the calculateAge() method can only be accessed within the package.

Protected

public class User {
protected int id;
protected string name;
protected Date birthday;
protected float age;

void calculateAge() { /*calculates the age from birthday*/ }
}
public class Employee extends User {

String getName() {
return super.name;
}
}

The property name can be accessed within the User class since it is a protected field. Consequently, child classes will also have access to protected properties.

Public

public class User {
private String name;

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

In this example, the name field is private to the User class, meaning it cannot be accessed from outside the class. However, the getName() method is public, allowing it to be used from anywhere.

Abstraction vs Encapsulation

Polymorphism

Polymorphism stands out as a cornerstone concept in Object-Oriented Programming (OOP), highlighting the capacity of an entity to manifest or exist in multiple forms. These diverse manifestations emerge as entities that can assume varied meanings and serve different purposes across various contexts.

The word polymorphism was also constructed using 2 words.

  • Poly — Means multiple
  • Morphism — Behaviours

For example, Cat, Dog, Cow are Animals. Here, we can take the Animal as the parent class and Cat, Dog, Cow as the child classes of Animal. All animals make sounds. But, differently. That is the concept of polymorphism.

In programming, mainly there are 2 types of polymorphism.

  1. Runtime polymorphism
  2. Compile-time polymorphism

Runtime polymorphism

Method overriding enables the attainment of runtime polymorphism. In Java, the Java Virtual Machine (JVM) identifies the correct method to invoke during runtime, not during compilation. This mechanism is also referred to as dynamic or late binding.

A behaviour of parent is replaced by the same behaviour of child.

interface Animal {
// abstract method without a body
public void speak();
}

class Cat implements Animal {
@Override
public void speak() {
// how cat speaks
System.out.println("Meow");
}
}

class Dog implements Animal {
@Override
public void speak() {
// how dog speaks
System.out.println("Woof");
}
}

In runtime polymorphism, only the method body will change. It does not change the method signature.

Compile-time polymorphism

Compile-time polymorphism is achieved by Method overloading. When an object is associated with its functionality during compilation, it is termed compile-time polymorphism. During compilation, Java determines which method to invoke based on method signatures, leading to compile-time polymorphism, also known as static or early binding.

public class Calculator {

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

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

}

In this example, you can observe that the Calculator class contains two distinct add() methods. It’s important to note that we cannot have the same method repeated within the same class. However, these methods are differentiated by their method signatures, making them distinct despite sharing the same name.

  • public int add(int a, int b)
  • public double add(double a, double b)

The first method accepts two integers as parameters and returns an integer, while the second method accepts two doubles as parameters and returns a double. During compile time, we can call either of these methods. The compiler determines which method to invoke based on the arguments provided when invoking these methods.

Compile Time vs Runtime polymorphism

Source: GeeksForGeeks

I hope this article has provided you with a simplified and comprehensive understanding of Object-Oriented Programming (OOP) concepts. Thank you for taking the time to read this article. May your journey in software development be filled with creativity and success!

Happy coding!

--

--

No responses yet