In object-oriented programming, you have classes and objects, unlike functional programming. There are 4 main pillars in OOP Paradigm.
- Abstraction
- Encapsulation
- Polymorphism
- Inheritance
Abstraction refers to the idea of identifying common properties and methods between similar objects and implementing them as abstract classes or interfaces.
By using abstract classes we can define, abstract methods as well as concrete methods and fields which are common throughout its inheritance.
It is possible to override and add additional functionality to concrete methods or even completely replacing its functionality but not calling super().
abstract class Shape {
protected final String name;
public Shape(String name) {
this.name = name;
}
public abstract int area();
public abstract int perimeter();
}
class Rectangle extends Shape {
private final int height;
private final int width;
public Rectangle(int height, int width) {
super("Rectangle");
this.height = height;
this.width = width;
}
@Override
public int area() {
return width * height;
}
@Override
public int perimeter() {
return 2 * (width + height);
}
}
class Circle extends Shape {
private final int radius;
public Circle(int radius) {
super("Circle");
this.radius = radius;
}
@Override
public int area() {
return (int) (Math.PI * radius * radius);
}
@Override
public int perimeter() {
return (int) (2 * Math.PI * radius);
}
}
class AbstractExample {
public static void main(String[] args) {
Shape rectangle = new Rectangle(5, 4);
Shape circle = new Circle(3);
System.out.println("Area of " + rectangle.name + " is " + rectangle.area());
System.out.println("Perimeter of " + rectangle.name + " is " + rectangle.perimeter());
System.out.println("Area of " + circle.name + " is " + circle.area());
System.out.println("Perimeter of " + circle.name + " is " + circle.perimeter());
}
}By using interfaces we can define a skeleton structure which inheriting class must implement.
interface Stack {
void pop();
void push(int data);
int top();
void print();
int size();
boolean isEmpty();
}
class StackUsingArray implements Stack {
private final int[] stack;
private final int capacity;
private int size = 0;
public StackUsingArray(int capacity) {
stack = new int[capacity];
this.capacity = capacity;
}
@Override
public void pop() {
if (size <= 0) {
throw new IllegalStateException("Stack is empty.");
}
size--;
}
@Override
public void push(int data) {
if (size >= capacity) {
throw new IllegalStateException("Stack is full.");
}
stack[size] = data;
size++;
}
@Override
public int top() {
if (size < 0) {
throw new IllegalStateException("Stack is empty");
}
return stack[size - 1];
}
@Override
public void print() {
for (int i = size - 1; i >= 0; i--) {
System.out.print(stack[i] + " ");
}
System.out.println();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
class StackUsingLinkedList implements Stack {
private DoublyNode head = null;
private DoublyNode last = null;
private int size = 0;
@Override
public void pop() {
last = last.prev;
size--;
}
@Override
public void push(int data) {
DoublyNode newNode = new DoublyNode(data);
if (head == null) {
head = newNode;
last = head;
} else {
newNode.prev = last;
last.next = newNode;
last = newNode;
}
size++;
}
@Override
public int top() {
return last.data;
}
@Override
public void print() {
DoublyNode temp = last;
while (temp != null) {
System.out.print(temp.data + " ");
temp = temp.prev;
}
System.out.println();
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
class StackExample {
public static void main(String[] args) {
testStack(new StackUsingArray(50));
testStack(new StackUsingLinkedList());
}
private static void testStack(Stack myStack) {
myStack.push(1);
myStack.push(2);
myStack.push(3);
System.out.print("Stack after push operations: ");
myStack.print();
System.out.println("Top element: " + myStack.top());
myStack.pop();
System.out.print("Stack after pop operation: ");
myStack.print();
System.out.println("Top element: " + myStack.top());
System.out.println("Size of the stack: " + myStack.size());
System.out.println("Is the stack empty? " + myStack.isEmpty());
}
}Suppose we have multiple fields for an object, let's take Employee as an example. An Employee can have id, name, salary etc. So with Data Encapsulation we can define a class which represents a Employee.
class Employee {
private final int id;
private final String name;
private final long salary;
public Employee(int id, String name, long salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public long getSalary() {
return salary;
}
@Override
public String toString() {
return "My name is " + getName() + ", my employee id is " + getId() + " and I make " + getSalary() + " USD";
}
}
class EncapsulationExample {
public static void main(String[] args) {
Employee johnDoe = new Employee(1, "John Doe", 1000);
Employee janeDoe = new Employee(2, "Jane Doe", 1200);
System.out.println(johnDoe.toString());
System.out.println(janeDoe.toString());
}
}As we understand from the name, something which can morph and change its form, we have abstract methods, and interfaces in java which are good example of polymorphism as they make use of method overriding and method overloading in some cases.
interface Addition {
int add(int a, int b);
double add(double a, double b);
}
class AdditionImpl implements Addition {
// Note the data-types, that's method overloading
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public double add(double a, double b) {
return a + b;
}
}
class PolymorphismExample {
public static void main(String[] args) {
Addition addition = new AdditionImpl();
System.out.println(addition.add(1, 2));
System.out.println(addition.add(1.0, 2.0));
}
}It refers to one class inheriting another class. There are 5 types of inheritance:
- Single level inheritance
- Multi level inheritance
- Hierarchical inheritance
- Multiple inheriance (not supported in Java)
- Hybrid inheritance