Skip to content

abdallahesham012/Learn-Java-OOP

Repository files navigation

Java OOP - Complete Object-Oriented Programming Guide

A comprehensive Java project demonstrating fundamental Object-Oriented Programming (OOP) concepts and SOLID principles with practical examples and real-world use cases.

Table of Contents


Project Overview

This project is a collection of educational modules that cover essential OOP concepts in Java. Each module includes working examples and demonstrates best practices in software design and architecture.

Target Audience: Beginner to Intermediate Java developers


Core Concepts

Call By Value & Reference

Location: CallByValueAndReference/

Overview

Demonstrates the difference between calling methods with primitive types (call by value) and objects (call by reference).

Key Classes

  • Main.java: Contains multiple overloaded change() methods showing:
    • Primitives (int, String): Called by value - changes inside methods don't affect original variables
    • Arrays: Called by reference - modifications inside methods affect the original array
    • Objects: Called by reference - modifications to object properties persist

Key Concepts

┌─────────────────────────────────────────┐
│  Call By Value vs Call By Reference     │
├─────────────────────────────────────────┤
│ Primitives (int, float, etc.) → VALUE   │
│ Objects → REFERENCE                     │
│ Arrays → REFERENCE                      │
│ Strings → VALUE (immutable)             │
└─────────────────────────────────────────┘

Example

int x = 5;
change(x);  // x remains 5 (call by value)

Reference obj = new Reference(1);
change(obj); // obj.x changes (call by reference)

Constructors

Location: Constructor/

Overview

Explores different types of constructors and their role in object initialization.

Key Classes

  • Constructor.java: Demonstrates:
    • Default constructor (no arguments)
    • Parameterized constructors (with arguments)
    • Constructor chaining (this())
    • Copy constructor

Constructor Types

Type Purpose Example
Default No arguments, initializes default values new Constructor()
Parameterized Takes arguments to initialize fields new Constructor(20, "Nada", 2000f)
Constructor Chaining Calls another constructor using this() Reduces code duplication
Copy Constructor Creates a new object copying data from existing new Constructor(ob4)

Properties Initialized

private int age;           // Default: 0
private String name;       // Default: null
private float amount;      // Default: 0.0f
private String color;      // Optional parameter

Example Usage

// Default constructor
Constructor ob = new Constructor();

// Parameterized constructor
Constructor ob2 = new Constructor(20);
Constructor ob3 = new Constructor(20, "Nada", 2000f);

// Constructor chaining
Constructor ob4 = new Constructor(52, "Ali", 200f, "Red");

// Copy constructor
Constructor ob5 = new Constructor(ob4);

Inheritance

Location: Inheritance/

Overview

Demonstrates class inheritance where a child class inherits properties and methods from a parent class.

Key Classes

  • Employee.java (Parent Class)

    • Properties: name, email, phone, department, address
    • Constructors: Default and parameterized
    • Methods: Getters and setters for all properties
  • Developer.java (Child Class)

    • Extends Employee
    • Additional property: projectName
    • Overridden/additional methods

Inheritance Hierarchy

      ┌─────────────┐
      │  Employee   │ (Parent)
      │  (Parent)   │
      └──────┬──────┘
             │
             │ extends
             │
      ┌──────▼──────┐
      │ Developer   │ (Child)
      │  (Child)    │
      └─────────────┘

Key Features

  • Access Modifier: protected allows child classes to access parent properties
  • Constructor Chaining: Child constructor can call parent constructor
  • Method Overriding: Child class can override parent methods
  • Polymorphism: Parent reference can hold child object

Example Usage

// Parent class
Employee emp = new Employee("Dodo", "abdohosh100@gmail.com", 50152933, "H1", "Cairo");

// Child class
Developer dev = new Developer("Dodo", "abdohosh100@gmail.com", 50152933, "H1", "Cairo", "OOP");
System.out.println(dev.getProjectName()); // OOP

Static Members

Location: Static_Members/

Overview

Demonstrates static variables and methods that belong to the class rather than individual objects.

Key Classes

  • Static.java: Student registration system showing:
    • Static variables: id counter and faculty constant
    • Static methods: validatePassword()
    • Instance variables: name, password

Static vs Instance

Feature Static Instance
Scope Belongs to class Belongs to object
Memory Shared by all objects Unique per object
Access ClassName.staticVar this.instanceVar
Initialization Once at class loading Each object creation

Key Concepts

public class Static {
    private static int id = 0;              // Static - shared by all
    private static String faculty = "CS";   // Static - shared by all
    private String name;                    // Instance - unique per object
    
    public static boolean validatePassword(String password) {
        return password.length() >= 6;  // Static method - no access to instance vars
    }
}

Example Usage

Static ob1 = new Static("Ali", "123589");
ob1.display();      // ID: 1

Static ob2 = new Static("Ahmed", "1244841289");
ob2.display();      // ID: 2 (ID incremented)

ob4.login(3, "18552003"); // Login successfully

SOLID Principles

The SOLID principles are five design principles that help create maintainable, scalable, and robust software. This project includes practical implementations of each principle.

Location: SOLID Principles/


Single Responsibility Principle (S)

Location: SOLID Principles/Sinlge Responsibility/

Principle: A class should have only one reason to change.

Overview

Each class should have a single, well-defined responsibility. This makes code easier to test, maintain, and understand.

Key Classes

  • Employee.java: Represents employee data
  • SalaryCalculator.java: Handles salary calculation logic
  • EmployeeRepository.java: Handles employee data persistence

Bad vs Good Design

❌ BAD DESIGN
┌──────────────────────────────────┐
│ Employee                         │
├──────────────────────────────────┤
│ - name                           │
│ - salary                         │
│ - calculateSalary()              │
│ - saveToDatabase()               │
│ - generateReport()               │
└──────────────────────────────────┘
(Multiple responsibilities - too many reasons to change)

✅ GOOD DESIGN
┌──────────────┐  ┌──────────────────┐  ┌──────────────────┐
│   Employee   │  │ SalaryCalculator │  │ EmployeeRepository
├──────────────┤  ├──────────────────┤  ├──────────────────┤
│ - name       │  │ - calculateSal.. │  │ - save()         │
│ - salary     │  │                  │  │ - update()       │
└──────────────┘  └──────────────────┘  └──────────────────┘
(Each class has ONE reason to change)

Example Implementation

// Employee - only data
public class Employee {
    private String name;
}

// SalaryCalculator - handles calculations
public class SalaryCalculator {
    public double calculateSalary(Employee emp) {
        return emp.getSalary() * 1.1; // 10% bonus
    }
}

// EmployeeRepository - handles persistence
public class EmployeeRepository {
    public void save(Employee emp) {
        // Save to database
    }
}

Benefits

Easier Testing: Test each responsibility independently
Maintainability: Changes to one responsibility don't affect others
Reusability: Classes can be reused in different contexts
Clarity: Easier to understand what each class does


Open/Closed Principle (O)

Location: SOLID Principles/Open_Closed/

Principle: Software entities should be open for extension but closed for modification.

Overview

Classes should be designed so new functionality can be added through extension, not by modifying existing code.

Key Classes

  • Shape.java: Abstract base class (interface)
  • Rectangle.java: Concrete implementation
  • Circle.java: Concrete implementation
  • AreaCalculator.java: Calculates area for any shape

Design Pattern

┌──────────────────────────┐
│     <<abstract>>         │
│        Shape            │
├──────────────────────────┤
│ + getArea(): double      │
└────────────┬─────────────┘
             │
      ┌──────┴──────┐
      │             │
┌─────▼─────┐  ┌────▼──────┐
│ Rectangle │  │  Circle   │
├───────────┤  ├───────────┤
│ - length  │  │ - radius  │
│ - width   │  │           │
└───────────┘  └───────────┘

┌──────────────────────────┐
│   AreaCalculator         │
├──────────────────────────┤
│ + calculate(shape):      │
│   return shape.area()    │
└──────────────────────────┘

Bad vs Good Design

❌ BAD DESIGN (Closed for extension)
calculateArea(shape):
  if shape is Rectangle:
    return length * width
  if shape is Circle:
    return π * r²
  (Every new shape requires modifying this method!)

✅ GOOD DESIGN (Open for extension)
calculateArea(shape):
  return shape.getArea()
  (Just add new Shape implementations!)

Example Implementation

// Abstract shape - closed for modification
public abstract class Shape {
    public abstract double getArea();
}

// Concrete implementations - open for extension
public class Rectangle extends Shape {
    private double length, width;
    
    @Override
    public double getArea() {
        return length * width;
    }
}

public class Circle extends Shape {
    private double radius;
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

// Calculator - unchanged, works with any shape
public class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.getArea();
    }
}

Usage

AreaCalculator calc = new AreaCalculator();

Shape rectangle = new Rectangle(5, 6.5);
System.out.println(calc.calculateArea(rectangle)); // 32.5

Shape circle = new Circle(7.9);
System.out.println(calc.calculateArea(circle)); // 196.1...

// Adding new shape doesn't require changing AreaCalculator!

Benefits

Low Risk: No modification to existing code = fewer bugs
Extensibility: Easy to add new shapes
Maintainability: Existing functionality stays stable
Code Reuse: Abstract class used with all implementations


Liskov Substitution Principle (L)

Location: SOLID Principles/Listkov Substitution/

Principle: Subtypes must be replaceable for their base types without breaking behavior.

Overview

Objects of a child class should be substitutable for objects of the parent class without altering the correctness of the program.

Key Classes

  • Shape.java: Base class with abstract methods
  • Rectangle.java: Child class implementation
  • Square.java: Child class implementation

The Classic Rectangle-Square Problem

Real-world relationship:
  Square IS-A Rectangle (mathematically true)

But in OOP:
  Square should NOT inherit from Rectangle
  (violates LSP because Square changes Rectangle's behavior)

Bad vs Good Design

❌ BAD DESIGN (Violates LSP)
Rectangle extends Shape:
  - setLength(double l)
  - setWidth(double w)

Square extends Rectangle:
  - setLength() → sets both length AND width
  - setWidth() → sets both length AND width
  (Square changes Rectangle's behavior - violates LSP!)

✅ GOOD DESIGN (Respects LSP)
Both inherit from Shape:
  - getArea() works correctly for each
  
No inheritance between Square and Rectangle
(Each implements getArea() according to its own rules)

Example Implementation

// Base class
public abstract class Shape {
    public abstract double getArea();
}

// Rectangle - standard behavior
public class Rectangle extends Shape {
    private double length, width;
    
    public void setLength(double l) { this.length = l; }
    public void setWidth(double w) { this.width = w; }
    
    @Override
    public double getArea() {
        return length * width;
    }
}

// Square - follows LSP
public class Square extends Shape {
    private double side;
    
    public void setSide(double s) { this.side = s; }
    
    @Override
    public double getArea() {
        return side * side;
    }
}

Usage

Shape shape = new Rectangle(5, 6);
System.out.println(shape.getArea()); // 30

Shape shape2 = new Square(10);
System.out.println(shape2.getArea()); // 100

// Both can be substituted for Shape without issues!

Benefits

Predictable Behavior: Child class behaves as expected
Polymorphism Safety: Can safely use parent references
Testing: Easier to test with polymorphic types
Reliability: No surprises when substituting objects


Interface Segregation Principle (I)

Location: SOLID Principles/interface Segregation/

Principle: Clients should not be forced to depend on interfaces they don't use.

Overview

Create multiple specific interfaces rather than one large general-purpose interface. Classes implement only the interfaces they need.

Key Classes

  • Workable.java: Interface for working entities
  • Eatable.java: Interface for entities that eat
  • Sleepable.java: Interface for entities that sleep
  • Human.java: Implements all interfaces (Workable, Eatable, Sleepable)
  • Robot.java: Implements only Workable interface

Design Pattern

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│   Workable   │  │   Eatable    │  │  Sleepable   │
├──────────────┤  ├──────────────┤  ├──────────────┤
│ + work()     │  │ + eat()      │  │ + sleep()    │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │
       │         ┌───────┴─────────────────┤
       │         │                         │
    ┌──▼─────────▼──────┐        ┌─────────▼────┐
    │     Human         │        │     Robot    │
    ├──────────────────┤        ├──────────────┤
    │ + work()         │        │ + work()     │
    │ + eat()          │        │              │
    │ + sleep()        │        │              │
    └──────────────────┘        └──────────────┘

Bad vs Good Design

❌ BAD DESIGN (Fat Interface)
interface Worker {
    void work();
    void eat();
    void sleep();
    void maintain();
}

Robot implements Worker {
    void work() { /* OK */ }
    void eat() { /* ERROR - Robot can't eat */ }
    void sleep() { /* ERROR - Robot doesn't sleep */ }
    void maintain() { /* OK */ }
}

✅ GOOD DESIGN (Segregated Interfaces)
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }

Human implements Workable, Eatable, Sleepable {
    void work() { /* OK */ }
    void eat() { /* OK */ }
    void sleep() { /* OK */ }
}

Robot implements Workable {
    void work() { /* OK */ }
}

Example Implementation

// Segregated interfaces
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

// Human implements all
public class Human implements Workable, Eatable, Sleepable {
    @Override
    public void work() { System.out.println("Working..."); }
    
    @Override
    public void eat() { System.out.println("Eating..."); }
    
    @Override
    public void sleep() { System.out.println("Sleeping..."); }
}

// Robot implements only what it needs
public class Robot implements Workable {
    @Override
    public void work() { System.out.println("Processing..."); }
}

Benefits

Flexibility: Classes implement only needed interfaces
No Bloated Interfaces: Each interface has focused responsibility
Cleaner Code: No empty/dummy method implementations
Better Design: Easier to understand and maintain


Dependency Inversion Principle (D)

Location: SOLID Principles/Dependency Inversion/

Principle: High-level modules should depend on abstractions, not concrete implementations.

Overview

Depend on interfaces/abstractions rather than concrete classes. This reduces coupling and improves flexibility.

Key Classes

  • Database.java: Abstract interface (abstraction layer)
  • MySqlDatabase.java: Concrete MySQL implementation
  • PostgresDatabase.java: Concrete PostgreSQL implementation
  • UserService.java: Depends on Database interface, not concrete classes

Design Pattern

Without Dependency Inversion (Tightly Coupled):
┌─────────────────┐
│  UserService    │
├─────────────────┤
│ - mySqlDb       │◄─────── Directly depends on
└────────┬────────┘         concrete class
         │
         ▼
    ┌──────────────────┐
    │ MySqlDatabase    │
    └──────────────────┘

With Dependency Inversion (Loosely Coupled):
┌─────────────────┐
│  UserService    │
├─────────────────┤
│ - database      │◄─────── Depends on
└────────┬────────┘         abstraction
         │
         ▼
    ┌──────────────────┐
    │  <<interface>>   │
    │    Database      │
    └────────┬─────────┘
             │
      ┌──────┴──────────┐
      │                 │
    ┌─▼───────────┐  ┌──▼─────────┐
    │ MySql       │  │ Postgres   │
    │ Database    │  │ Database   │
    └─────────────┘  └────────────┘

Bad vs Good Design

❌ BAD DESIGN (Tightly Coupled)
class UserService {
    private MySqlDatabase db = new MySqlDatabase();
    
    void process() {
        db.connect();
        db.save();
    }
}
// Problem: Hard-coded dependency on MySqlDatabase
// Can't switch to PostgreSQL without changing code

✅ GOOD DESIGN (Loosely Coupled)
interface Database {
    void connect();
    void save();
}

class UserService {
    private Database db;  // Depends on abstraction
    
    public UserService(Database db) {
        this.db = db;  // Inject concrete implementation
    }
    
    void process() {
        db.connect();
        db.save();
    }
}
// Easy to switch implementations at runtime

Example Implementation

// Abstraction layer
public interface Database {
    void connect();
    void disconnect();
    void save();
    void retrieve();
}

// Concrete implementation 1
public class MySqlDatabase implements Database {
    @Override
    public void connect() { System.out.println("Connecting to MySQL..."); }
    
    @Override
    public void save() { System.out.println("Saving to MySQL..."); }
    
    // ... other methods
}

// Concrete implementation 2
public class PostgresDatabase implements Database {
    @Override
    public void connect() { System.out.println("Connecting to PostgreSQL..."); }
    
    @Override
    public void save() { System.out.println("Saving to PostgreSQL..."); }
    
    // ... other methods
}

// High-level module depends on abstraction
public class UserService {
    private Database database;
    
    // Constructor injection
    public UserService(Database database) {
        this.database = database;
    }
    
    public void process() {
        database.connect();
        database.save();
        database.disconnect();
    }
}

Usage

// Easy to switch implementations
Database db1 = new MySqlDatabase();
UserService service1 = new UserService(db1);
service1.process();

// Just switch the implementation
Database db2 = new PostgresDatabase();
UserService service2 = new UserService(db2);
service2.process();

Benefits

Flexibility: Easy to switch implementations
Testability: Can inject mock objects for testing
Loose Coupling: High-level modules independent of implementations
Maintainability: Changes to one implementation don't affect others


Project Structure

Java_OOP/
├── README.md
│
├── CallByValueAndReference/
│   ├── src/
│   │   ├── Main.java
│   │   └── Refrence.java
│   └── CallByValueAndReference.iml
│
├── Constructor/
│   ├── src/
│   │   ├── Constructor.java
│   │   └── Main.java
│   └── Constructor.iml
│
├── Inheritance/
│   ├── src/
│   │   ├── Employee.java (Parent class)
│   │   ├── Developer.java (Child class)
│   │   └── Main.java
│   └── Inheritance.iml
│
├── Static_Members/
│   ├── src/
│   │   ├── Static.java
│   │   └── Main.java
│   └── Static_Members.iml
│
└── SOLID Principles/
    ├── src/
    │   └── Main.java
    │
    ├── Single Responsibility/
    │   ├── src/
    │   │   ├── Employee.java
    │   │   ├── SalaryCalculator.java
    │   │   ├── EmployeeRepository.java
    │   │   └── Main.java
    │   └── Single Responsibility.iml
    │
    ├── Open_Closed/
    │   ├── src/
    │   │   ├── Shape.java (Abstract base)
    │   │   ├── Rectangle.java
    │   │   ├── Circle.java
    │   │   ├── AreaCalculator.java
    │   │   └── Main.java
    │   └── Open_Closed.iml
    │
    ├── Listkov Substitution/
    │   ├── src/
    │   │   ├── Shape.java (Abstract base)
    │   │   ├── Rectangle.java
    │   │   ├── Square.java
    │   │   └── Main.java
    │   └── Listkov Substitution.iml
    │
    ├── Interface Segregation/
    │   ├── src/
    │   │   ├── Workable.java (Interface)
    │   │   ├── Eatable.java (Interface)
    │   │   ├── Sleepable.java (Interface)
    │   │   ├── Human.java
    │   │   ├── Robot.java
    │   │   └── Main.java
    │   └── Interface Segregation.iml
    │
    └── Dependency Inversion/
        ├── src/
        │   ├── Database.java (Interface)
        │   ├── MySqlDatabase.java
        │   ├── PostgresDatabase.java
        │   ├── UserService.java
        │   └── Main.java
        └── Dependency Inversion.iml

Usage Guide

Prerequisites

  • Java Development Kit (JDK) 8 or higher
  • IntelliJ IDEA or any Java IDE

Running Examples

1. Call By Value & Reference

cd CallByValueAndReference
javac src/*.java
java Main

Output:

5           # x unchanged (call by value)
Ali         # string unchanged
null        # wrapper unchanged
1 2 3       # array changed (call by reference)
2           # object changed (call by reference)

2. Constructors

cd Constructor
javac src/*.java
java Main

Output:

age is 0
name is null
amount is 0.0
# ... (multiple constructor outputs)

3. Inheritance

cd Inheritance
javac src/*.java
java Main

Output:

I'm Parent Class
0
null
# ... (inherited properties and methods)

4. Static Members

cd Static_Members
javac src/*.java
java Main

Output:

Done register
name is Ali
ID is 1
Faculty is Computer Science

Done register
name is Ahmed
ID is 2
Faculty is Computer Science

5. SOLID Principles

cd "SOLID Principles/Single Responsibility"
javac src/*.java
java Main

Output: (Varies by principle)


Key Learnings

1. Call By Value vs Reference

  • Primitive types are passed by value
  • Objects and arrays are passed by reference
  • Strings are immutable (behave like primitives)

2. Constructors

  • Default constructor is called if you don't define one
  • Parameterized constructors allow flexible initialization
  • Constructor chaining (this()) reduces code duplication
  • Copy constructors create independent copies

3. Inheritance

  • Reduces code duplication through code reuse
  • Parent class properties/methods inherited by child
  • Child can override parent methods
  • protected modifier allows controlled access

4. Static Members

  • Shared by all instances of a class
  • Accessed via ClassName.staticVar
  • Useful for counters, constants, utility methods
  • Instance variables use this.var

5. SOLID Principles

Principle Key Takeaway
S One class = One responsibility
O Add features via extension, not modification
L Child can safely replace parent
I Small focused interfaces, not fat ones
D Depend on abstractions, not concrete classes

6. Design Patterns

  • Abstraction: Use interfaces and abstract classes
  • Polymorphism: Code to the interface, not implementation
  • Composition: Consider over inheritance
  • Dependency Injection: Pass dependencies, don't create them

Best Practices

  1. Use Access Modifiers Wisely

    • private for internal state
    • protected for inheritance
    • public for public API
  2. Favor Composition Over Inheritance

    • Composition is more flexible
    • Inheritance creates tight coupling
  3. Program to Interfaces

    • Depend on abstractions
    • Makes code testable and flexible
  4. Keep It Simple

    • Single Responsibility Principle
    • Avoid over-engineering
  5. Test Your Code

    • Unit test each class
    • Mock dependencies using interfaces

Contributing

Feel free to:

  • Fork this repository
  • Add more examples
  • Improve documentation
  • Create pull requests

License

This project is open source and available for educational purposes.


Author Notes

This project demonstrates practical implementations of OOP concepts and SOLID principles. Each module is self-contained and can be studied independently. The examples are designed to be clear and educational rather than production-ready.

Happy Learning!


About

Learn Java OOP concepts with practical examples and exercises.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages