Object Oriented Programming

#Q #AOSR/4c80u
Object-oriented programming (OOP) :: is a programming paradigm that is centered around the concept of "objects". #AOSR/4c80u/s/p6el
#/Q

#Q #AOSR/4dqq8
An object :: is a self-contained unit that represents a real-world entity or concept and contains both data (attributes) and the methods (functions) that operate on that data. #AOSR/4dqq8/s/2e9k
#/Q

Inheritance

#Q #AOSR/29k0u
Inheritance :: allows a class to acquire the properties (attributes and methods) of another class. It establishes a hierarchical relationship between classes, where one class, known as the "subclass" or "derived class," can inherit the members of another class, known as the "superclass" or "base class." #AOSR/29k0u/sr/7gvl #AOSR/29k0u/s/73a4
#/Q

Key Points

  1. Code Reuse: Inheritance enables the reuse of code.

  2. "is-a" Relationship: Inheritance typically represents an "is-a" relationship between the subclass and the superclass.

  3. Inherited Members: The subclass inherits all the attributes and methods of the superclass. It can use and extend them as needed.

  4. Method Overriding: In some cases, a subclass may provide its own implementation of a method that is already defined in the superclass.

class Vehicle {
    String make;
    String model;
    String type;

    public Vehicle(String make, String model, String type) {
        this.make = make;
        this.model = model;
        this.type = type;
    }
    

    public void displayInfo() {
        System.out.println("Make: " + make + ", Model: " + model + "Type: " + type);
    }
    
}

class Car extends Vehicle {
    String color;

    public Car(String make, String model, String type,String color) {
        super(make, model,type); // Call the superclass constructor
        this.color = color;
    }
   

    @Override
    public void displayInfo() {
        // Override the superclass method
        System.out.println("Make: " + make + ", Model: " + model + "Type:" + type + ", Color: " + color);
    }
}

public class InheritanceExample {
    public static void main(String[] args) {
        Car car = new Car("Toyota", "Camry","Meow", "Blue"); // This is where we instantiate the object car
        car.displayInfo();  // Calls the overridden method in the Car class
    }
}

Polymorphism

#Q #AOSR/2dvig
Polymorphism :: is the task that performs a single action in different ways. #AOSR/2dvig/s/3tvh
#/Q

It enables flexibility and extensibility in code by allowing multiple classes to be used interchangeably when they share a common interface or base class.

Types

#Q #AOSR/556l4

  1. Compile-Time (Static) Polymorphism :: This is achieved through method overloading or operator overloading. In this type of polymorphism, the method or operator to be invoked is determined at compile time based on the method or function signature. #AOSR/556l4/s/1p6s
    #/Q
class Calculator {
    // Method to add two integers
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    // Method to add three integers
    public int add(int num1, int num2, int num3) {
        return num1 + num2 + num3;
    }

    // Method to add two double values
    public double add(double num1, double num2) {
        return num1 + num2;
    }
}

public class CompileTimePolymorphismExample {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        int sum1 = calculator.add(5, 10);    // Calls the first add method
        int sum2 = calculator.add(5, 10, 15);// Calls the second add method
        double sum3 = calculator.add(3.5, 2.7);// Calls the third add method

        System.out.println("Sum1: " + sum1);
        System.out.println("Sum2: " + sum2);
        System.out.println("Sum3: " + sum3);
    }
}

#Q
2. Run-Time (Dynamic) Polymorphism :: This is achieved through method overriding. In run-time polymorphism, the method to be executed is determined at runtime based on the actual object type rather than the reference type.
#/Q

class Animal {
	String type;
	
	public Animal(String type) {
		this.type=type;
	}
    public void makeSound() {
        System.out.println("Some generic animal sound");
    }
}

class Dog extends Animal {
	
	public Dog(String type) {
		super(type);
	}
    @Override
    public void makeSound() {
        System.out.println("Woof! Woof!");
    }
}

class Cat extends Animal {
	
	public Cat(String type) {
		super(type);
	}
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

class Cow extends Animal {
	int age;
	String id;
	
	public Cow(int age, String id, String type) {
		super(type);
		this.age=age;
		this.id=id;
	}
	@Override
	public void makeSound() {
		System.out.println("Mooo Mooo");
	}
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal myDog = new Dog("big");
        Animal myCat = new Cat("small");
        Animal myCow = new Cow(15,"whatever","small");
        Animal myCow1 = new Cow(16,"whatever2","big");
        
        myDog.makeSound(); // Calls Dog's makeSound() method
        myCat.makeSound(); // Calls Cat's makeSound() method
	    myCow1.makeSound(); // Calls Cow's makeSound() method
    }
}

Encapsulation

#Q
Encapsulation :: This is a concept that involves bundling the data (attributes) and the methods (functions) that operate on that data into a single unit, known as an "object."
#/Q

In encapsulation, the variables of a class will be hidden from other classes and can be accessed only through the methods of their class. Therefore, it is also known as data/information hiding.

Goals

#Q
Goals of encapsulation would be ::

  1. Data Protection: Encapsulation helps protect the integrity of the data by preventing direct access or modification from outside the object.
  2. Modularity: Encapsulation promotes modular design, as objects can be treated as black boxes, with their internal details hidden.
  3. Abstraction: Encapsulation allows you to abstract the essential characteristics of an object, focusing on what an object does rather than how it does it.
    #/Q
public class Car {
    private String make;  // Private attribute
    private String model; // Private attribute

    public Car(String make, String model) {
        this.make = make;
        this.model = model;
    }

    // Public getter methods to access private attributes
    public String getMake() {
        return make;
    }

    public String getModel() {
        return model;
    }

    // Public setter methods to modify private attributes
    public void setMake(String make) {
        this.make = make;
    }

    public void setModel(String model) {
        this.model = model;
    }	
}


public class mainCar {
    public static void main(String[] args) {
	    Car car = new Car("Toyata","Car");
	    //System.out.println(car.make) // this is not possible with encapsulation
	    //System.out.println(car.getMake()); // this is  possible with encapsulation
	    
    }
}

Abstraction

#Q #AOSR/65oo6

Key Points

#Q

  1. Hiding Complex Implementation :: Abstraction involves hiding the complex implementation details of an object or system while exposing a simplified interface that is easy to use.
    #/Q

#Q
2. Defining a Blueprint :: Abstraction often involves defining classes that serve as blueprints for objects.
#/Q

#Q
3. Focusing on Relevant Details :: Abstraction helps to concentrate on the aspects of an object that are relevant to the problem, and ignore or encapsulate away the less relevant details.
#/Q

#Q
4. Promoting Reusability :: By creating abstract classes and interfaces, we can create a foundation for similar objects with common characteristics, promoting code reusability.
#/Q

#Q
5. Enhancing Maintainability :: Abstraction can make code more maintainable because it reduces complexity and provides a clear separation between what an object does and how it does it.
#/Q

abstract class Shape {
    abstract double calculateArea();
    abstract double calculatePerimeter();
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double calculateArea() {
        return Math.PI * radius * radius;
    }

    @Override
    double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
}
class Rectanlge extends Shape {
    private double height;
    private double width;

    public Rectangle(double height,double width) {
        this.width = width;
        this.height = height;
    }

    @Override
    double calculateArea() {
        return width*height
    }

    @Override
    double calculatePerimeter() {
        return width*2+height*2
    }
}

Abstract Classes

#Q #AOSR/3q01a
An abstract class :: is a class that cannot be instantiated on its own. It serves as a blueprint for other classes and can contain a mix of abstract and concrete (implemented) methods. #AOSR/3q01a/s/4jke
#/Q

They can be extended by other classes. A subclass that extends an abstract class is required to provide concrete implementations for all the abstract methods defined in the abstract class.

abstract class Shape {
    abstract double calculateArea();
    abstract double calculatePerimeter();
}

Interface

#Q #AOSR/2onto
An interface :: is a collection of abstract methods that provide a contract that classes must adhere to. An interface cannot contain any implementation details; it defines the methods a class implementing the interface must have. #AOSR/2onto/s/z417
#/Q

Classes can implement multiple interfaces. Implementing an interface means that the implementing class must provide concrete implementations for all the methods defined in the interface.

interface Drawable {
    void draw();
}

Multi-Threaded Programming

#Q
This is a programming technique that allows a computer program or process to execute multiple threads concurrently.
#/Q

Multithreading enables a program to perform multiple tasks or operations in parallel, taking advantage of the available CPU cores and resources.

Definition

A thread is the smallest unit of a CPU's execution that can be independently scheduled by the operating system.

Key Points

#Q #AOSR/6j8uf

  1. Threads :: A thread is a lightweight, independent, and separate path of execution within a program. Each thread has its own stack, program counter, and register state but shares the same memory space as other threads in the same process. #AOSR/6j8uf/s/6bmn
    #/Q

#Q #AOSR/7th7j
2. Concurrent Execution :: Multithreading enables concurrent execution of threads. #AOSR/7th7j/s/3d2i
#/Q

#Q #AOSR/51tl6
3. Thread Synchronization :: Because threads share the same memory space, developers need to use synchronization mechanisms to coordinate the access to shared data to avoid race conditions and ensure data consistency. #AOSR/51tl6/s/n1ks
#/Q

Life Cycle of Thread

Screenshot .png

The life cycle of a thread can be described as follows:
#Q #AOSR/lku94

  1. New :: In this state, a thread is created but has not yet started its execution. The thread is considered new, and its resources are not allocated or initialized. #AOSR/lku94/s/5i0p
    #/Q
    #Q #AOSR/vnjst
  2. Runnable :: A thread in this state is ready to run but may not be currently executing. The operating system's scheduler will determine when the thread gets CPU time. The thread may be executing, waiting for its turn, or waiting for a resource. If the thread is waiting for a resource, it moves to the "Blocked" state. #AOSR/vnjst/s/4bsm
    #/Q
    #Q #AOSR/2u48m
  3. Waiting :: Threads can enter the "Waiting" state when they explicitly wait for some condition to be met, such as waiting for another thread to notify them. #AOSR/2u48m/s/6dve
    #/Q
    #Q #AOSR/5las9
  4. Timed Waiting :: This state is similar to the "Waiting" state, but it has a time limit. Threads enter the "Timed Waiting" state when they wait for a specific period or timeout. Common examples include using the sleep() method or specifying a timeout for methods like wait(). #AOSR/5las9/s/p9bg
    #/Q
    #Q #AOSR/3mi75
  5. Terminated (Dead) :: A thread enters the "Terminated" state when it has completed its execution or when an unhandled exception terminates it. In this state, the thread's resources are released, and it can no longer be started or run. #AOSR/3mi75/s/5sqm
    #/Q

Using the Thread class:

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getId() + ": " + i);
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        
        // Start the threads
        thread1.start();
        thread2.start();
    }
}

Using the Runnable interface:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getId() + ": " + i);
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);
        
        // Start the threads
        thread1.start();
        thread2.start();
    }
}

Synchronized Thread

class SharedResource {
    // Shared resource (a simple counter)
    private int count = 0;

    // Synchronize the increment operation
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

class MyThread extends Thread {
    private SharedResource sharedResource;

    public MyThread(SharedResource sharedResource) {
        this.sharedResource = sharedResource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            sharedResource.increment();
        }
    }
}

public class ThreadSynchronizationExample {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();
        
        MyThread thread1 = new MyThread(sharedResource);
        MyThread thread2 = new MyThread(sharedResource);
        
        thread1.start();
        thread2.start();
        
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Final Count: " + sharedResource.getCount());
    }
}