Skip to main content

Java Object-Oriented Programming

Table of Contents

  1. Introduction to OOP
  2. Classes and Objects
  3. Constructors
  4. Instance Variables and Methods
  5. Static Members
  6. Encapsulation
  7. Inheritance
  8. Polymorphism
  9. Abstraction
  10. Interfaces
  11. Access Modifiers
  12. Method Overloading and Overriding
  13. Super Keyword
  14. Final Keyword
  15. Practical Examples

Introduction to OOP

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects" which contain data (attributes) and code (methods). Java is a pure object-oriented language where everything is an object (except primitives).

Core Principles of OOP

  1. Encapsulation - Bundling data and methods that work on that data within one unit
  2. Inheritance - Creating new classes based on existing classes
  3. Polymorphism - One interface, multiple implementations
  4. Abstraction - Hiding implementation details and showing only functionality

Benefits of OOP

  • Modularity - Code is organized into discrete objects
  • Reusability - Objects can be reused across different programs
  • Maintainability - Easy to modify and extend
  • Scalability - Easy to add new features
  • Security - Data hiding through encapsulation

Real-World Analogy

Think of a Car as an object:

  • Attributes: color, model, speed, fuel
  • Methods: start(), stop(), accelerate(), brake()
  • Encapsulation: Internal engine details are hidden
  • Inheritance: SportsCar extends Car
  • Polymorphism: Different cars implement start() differently

Classes and Objects

What is a Class?

A class is a blueprint or template for creating objects. It defines the structure and behavior that objects of that type will have.

What is an Object?

An object is an instance of a class. It's a concrete entity created from the class blueprint.

Basic Class Structure

// Class definition
public class Car {
// Instance variables (attributes/properties)
private String brand;
private String model;
private int year;
private double price;

// Constructor
public Car(String brand, String model, int year, double price) {
this.brand = brand;
this.model = model;
this.year = year;
this.price = price;
}

// Instance methods (behavior)
public void startEngine() {
System.out.println(brand + " " + model + " engine started!");
}

public void displayInfo() {
System.out.println("Car: " + brand + " " + model + " (" + year + ") - $" + price);
}

// Getter methods
public String getBrand() {
return brand;
}

public String getModel() {
return model;
}
}

// Using the class to create objects
public class CarDemo {
public static void main(String[] args) {
// Creating objects (instantiation)
Car car1 = new Car("Toyota", "Camry", 2023, 28000.0);
Car car2 = new Car("Honda", "Civic", 2022, 25000.0);

// Using objects
car1.displayInfo();
car1.startEngine();

car2.displayInfo();
car2.startEngine();

// Accessing object properties through methods
System.out.println("Car 1 brand: " + car1.getBrand());
}
}

Multiple Objects Example

public class Student {
private String name;
private int age;
private String studentId;
private double gpa;

public Student(String name, int age, String studentId) {
this.name = name;
this.age = age;
this.studentId = studentId;
this.gpa = 0.0; // Default value
}

public void study(String subject) {
System.out.println(name + " is studying " + subject);
}

public void setGpa(double gpa) {
if (gpa >= 0.0 && gpa <= 4.0) {
this.gpa = gpa;
} else {
System.out.println("Invalid GPA! Must be between 0.0 and 4.0");
}
}

public String getGradeLevel() {
if (gpa >= 3.5) return "Excellent";
else if (gpa >= 3.0) return "Good";
else if (gpa >= 2.0) return "Average";
else return "Needs Improvement";
}

public void displayInfo() {
System.out.printf("Student: %s (ID: %s), Age: %d, GPA: %.2f (%s)%n",
name, studentId, age, gpa, getGradeLevel());
}

// Getters
public String getName() { return name; }
public int getAge() { return age; }
public String getStudentId() { return studentId; }
public double getGpa() { return gpa; }
}

public class StudentDemo {
public static void main(String[] args) {
// Creating multiple student objects
Student[] students = {
new Student("Alice Johnson", 20, "S001"),
new Student("Bob Smith", 19, "S002"),
new Student("Carol Davis", 21, "S003")
};

// Setting GPAs
students[0].setGpa(3.8);
students[1].setGpa(3.2);
students[2].setGpa(2.9);

// Display all students
System.out.println("=== Student Information ===");
for (Student student : students) {
student.displayInfo();
student.study("Java Programming");
System.out.println();
}
}
}

Constructors

Constructors are special methods used to initialize objects when they are created. They have the same name as the class and no return type.

Types of Constructors

public class Rectangle {
private double length;
private double width;
private String color;

// 1. Default Constructor (no parameters)
public Rectangle() {
this.length = 1.0;
this.width = 1.0;
this.color = "white";
System.out.println("Default constructor called");
}

// 2. Parameterized Constructor
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
this.color = "white"; // Default color
System.out.println("Parameterized constructor (2 params) called");
}

// 3. Constructor Overloading
public Rectangle(double length, double width, String color) {
this.length = length;
this.width = width;
this.color = color;
System.out.println("Parameterized constructor (3 params) called");
}

// 4. Copy Constructor
public Rectangle(Rectangle other) {
this.length = other.length;
this.width = other.width;
this.color = other.color;
System.out.println("Copy constructor called");
}

public double calculateArea() {
return length * width;
}

public double calculatePerimeter() {
return 2 * (length + width);
}

public void displayInfo() {
System.out.printf("Rectangle: %.2f x %.2f, Color: %s%n", length, width, color);
System.out.printf("Area: %.2f, Perimeter: %.2f%n", calculateArea(), calculatePerimeter());
}

// Getters and Setters
public double getLength() { return length; }
public double getWidth() { return width; }
public String getColor() { return color; }

public void setLength(double length) {
if (length > 0) this.length = length;
}

public void setWidth(double width) {
if (width > 0) this.width = width;
}

public void setColor(String color) {
this.color = color;
}
}

public class ConstructorDemo {
public static void main(String[] args) {
System.out.println("Creating rectangles using different constructors:\n");

// Using different constructors
Rectangle rect1 = new Rectangle(); // Default
Rectangle rect2 = new Rectangle(5.0, 3.0); // 2 parameters
Rectangle rect3 = new Rectangle(4.0, 6.0, "blue"); // 3 parameters
Rectangle rect4 = new Rectangle(rect2); // Copy constructor

System.out.println("\nRectangle Information:");
rect1.displayInfo();
System.out.println();

rect2.displayInfo();
System.out.println();

rect3.displayInfo();
System.out.println();

rect4.displayInfo();
}
}

Constructor Chaining

public class Employee {
private String name;
private int id;
private double salary;
private String department;

// Constructor chaining using this()
public Employee() {
this("Unknown", 0); // Calls the two-parameter constructor
}

public Employee(String name, int id) {
this(name, id, 0.0); // Calls the three-parameter constructor
}

public Employee(String name, int id, double salary) {
this(name, id, salary, "General"); // Calls the four-parameter constructor
}

public Employee(String name, int id, double salary, String department) {
this.name = name;
this.id = id;
this.salary = salary;
this.department = department;
System.out.println("Main constructor called for: " + name);
}

public void displayEmployee() {
System.out.printf("Employee: %s (ID: %d), Department: %s, Salary: $%.2f%n",
name, id, department, salary);
}
}

Instance Variables and Methods

Instance Variables

Instance variables belong to specific objects and each object has its own copy.

Instance Methods

Instance methods can access instance variables and other instance methods directly.

public class BankAccount {
// Instance variables - each account has its own values
private String accountNumber;
private String accountHolder;
private double balance;
private String accountType;
private int transactionCount;

public BankAccount(String accountNumber, String accountHolder, String accountType) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.accountType = accountType;
this.balance = 0.0;
this.transactionCount = 0;
}

// Instance methods - operate on instance variables
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
transactionCount++;
System.out.printf("Deposited $%.2f. New balance: $%.2f%n", amount, balance);
} else {
System.out.println("Invalid deposit amount!");
}
}

public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
transactionCount++;
System.out.printf("Withdrew $%.2f. New balance: $%.2f%n", amount, balance);
} else if (amount > balance) {
System.out.println("Insufficient funds!");
} else {
System.out.println("Invalid withdrawal amount!");
}
}

public void transfer(BankAccount targetAccount, double amount) {
if (amount > 0 && amount <= balance) {
this.withdraw(amount);
targetAccount.deposit(amount);
System.out.printf("Transferred $%.2f to account %s%n",
amount, targetAccount.accountNumber);
} else {
System.out.println("Transfer failed!");
}
}

public double calculateInterest() {
double interestRate = accountType.equals("Savings") ? 0.03 : 0.01;
return balance * interestRate;
}

public void displayAccountInfo() {
System.out.println("=== Account Information ===");
System.out.println("Account Number: " + accountNumber);
System.out.println("Account Holder: " + accountHolder);
System.out.println("Account Type: " + accountType);
System.out.printf("Current Balance: $%.2f%n", balance);
System.out.println("Transaction Count: " + transactionCount);
System.out.printf("Potential Interest: $%.2f%n", calculateInterest());
}

// Getters
public String getAccountNumber() { return accountNumber; }
public String getAccountHolder() { return accountHolder; }
public double getBalance() { return balance; }
public String getAccountType() { return accountType; }
}

public class BankingDemo {
public static void main(String[] args) {
// Creating bank account objects
BankAccount alice = new BankAccount("ACC001", "Alice Johnson", "Savings");
BankAccount bob = new BankAccount("ACC002", "Bob Smith", "Checking");

System.out.println("=== Banking Operations Demo ===\n");

// Operations on Alice's account
alice.deposit(1000.0);
alice.withdraw(200.0);
alice.displayAccountInfo();

System.out.println();

// Operations on Bob's account
bob.deposit(500.0);
bob.displayAccountInfo();

System.out.println();

// Transfer between accounts
alice.transfer(bob, 150.0);

System.out.println("\nAfter transfer:");
alice.displayAccountInfo();
System.out.println();
bob.displayAccountInfo();
}
}

Static Members

Static members belong to the class rather than to any specific object. They are shared among all objects of the class.

Static Variables and Methods

public class Counter {
// Static variable - shared by all objects
private static int totalCount = 0;

// Instance variable - unique to each object
private int instanceCount;
private String name;

public Counter(String name) {
this.name = name;
this.instanceCount = 0;
totalCount++; // Increment static variable
System.out.println("Counter created for: " + name);
}

// Instance method
public void increment() {
instanceCount++;
totalCount++;
}

// Static method - can only access static variables
public static int getTotalCount() {
return totalCount;
}

// Static method to reset total count
public static void resetTotalCount() {
totalCount = 0;
System.out.println("Total count reset to 0");
}

// Instance method
public void displayInfo() {
System.out.printf("%s - Instance: %d, Total: %d%n",
name, instanceCount, totalCount);
}

public int getInstanceCount() {
return instanceCount;
}
}

public class StaticDemo {
public static void main(String[] args) {
System.out.println("Initial total count: " + Counter.getTotalCount());

// Creating objects
Counter counter1 = new Counter("Counter1");
Counter counter2 = new Counter("Counter2");
Counter counter3 = new Counter("Counter3");

System.out.println("After creating 3 counters: " + Counter.getTotalCount());

// Using instance methods
counter1.increment();
counter1.increment();
counter2.increment();

System.out.println("\nAfter some increments:");
counter1.displayInfo();
counter2.displayInfo();
counter3.displayInfo();

System.out.println("Total count: " + Counter.getTotalCount());

// Using static method
Counter.resetTotalCount();
System.out.println("After reset: " + Counter.getTotalCount());
}
}

Static Block and Utility Class

public class MathUtils {
// Static variables
private static final double PI = 3.14159;
private static boolean initialized = false;

// Static block - executes when class is first loaded
static {
System.out.println("Loading MathUtils class...");
initialized = true;
System.out.println("MathUtils initialized successfully!");
}

// Static utility methods
public static double calculateCircleArea(double radius) {
return PI * radius * radius;
}

public static double calculateRectangleArea(double length, double width) {
return length * width;
}

public static boolean isPrime(int number) {
if (number < 2) return false;
for (int i = 2; i <= Math.sqrt(number); i++) {
if (number % i == 0) return false;
}
return true;
}

public static int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

public static boolean isInitialized() {
return initialized;
}
}

public class StaticUtilityDemo {
public static void main(String[] args) {
// Static block executes automatically
System.out.println("MathUtils initialized: " + MathUtils.isInitialized());

// Using static utility methods
System.out.println("\n=== Math Utilities ===");
System.out.println("Circle area (radius 5): " + MathUtils.calculateCircleArea(5.0));
System.out.println("Rectangle area (4x6): " + MathUtils.calculateRectangleArea(4.0, 6.0));
System.out.println("Is 17 prime? " + MathUtils.isPrime(17));
System.out.println("Is 20 prime? " + MathUtils.isPrime(20));
System.out.println("Factorial of 5: " + MathUtils.factorial(5));
}
}

Encapsulation

Encapsulation is the bundling of data (attributes) and methods (functions) that operate on the data into a single unit, and restricting access to some of the object's components. It's implemented using access modifiers and getter/setter methods.

Benefits of Encapsulation

  • Data Protection: Prevents direct access to sensitive data
  • Validation: Control how data is set and retrieved
  • Maintainability: Changes to internal implementation don't affect external code
  • Flexibility: Can change internal representation without affecting users
public class Person {
// Private instance variables - encapsulated data
private String name;
private int age;
private String email;
private double salary;
private boolean isEmployed;

// Constructor
public Person(String name, int age, String email) {
setName(name);
setAge(age);
setEmail(email);
this.salary = 0.0;
this.isEmployed = false;
}

// Getter methods (accessors) - provide controlled access to read data
public String getName() {
return name;
}

public int getAge() {
return age;
}

public String getEmail() {
return email;
}

public double getSalary() {
return isEmployed ? salary : 0.0; // Only return salary if employed
}

public boolean isEmployed() {
return isEmployed;
}

// Setter methods (mutators) - provide controlled access to modify data
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name.trim();
} else {
throw new IllegalArgumentException("Name cannot be null or empty");
}
}

public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
}

public void setEmail(String email) {
if (email != null && email.contains("@") && email.contains(".")) {
this.email = email.toLowerCase();
} else {
throw new IllegalArgumentException("Invalid email format");
}
}

public void setSalary(double salary) {
if (salary >= 0) {
this.salary = salary;
} else {
throw new IllegalArgumentException("Salary cannot be negative");
}
}

public void setEmployed(boolean employed) {
this.isEmployed = employed;
if (!employed) {
this.salary = 0.0; // Reset salary when unemployed
}
}

// Business logic methods
public void hire(double salary) {
setEmployed(true);
setSalary(salary);
System.out.println(name + " has been hired with salary: $" + salary);
}

public void fire() {
setEmployed(false);
System.out.println(name + " has been terminated");
}

public void giveRaise(double percentage) {
if (isEmployed && percentage > 0) {
double newSalary = salary * (1 + percentage / 100);
setSalary(newSalary);
System.out.printf("%s received a %.1f%% raise. New salary: $%.2f%n",
name, percentage, salary);
} else {
System.out.println("Cannot give raise: person not employed or invalid percentage");
}
}

public String getAgeGroup() {
if (age < 13) return "Child";
else if (age < 20) return "Teenager";
else if (age < 60) return "Adult";
else return "Senior";
}

public void displayInfo() {
System.out.println("=== Person Information ===");
System.out.println("Name: " + name);
System.out.println("Age: " + age + " (" + getAgeGroup() + ")");
System.out.println("Email: " + email);
System.out.println("Employment Status: " + (isEmployed ? "Employed" : "Unemployed"));
if (isEmployed) {
System.out.printf("Salary: $%.2f%n", salary);
}
}
}

public class EncapsulationDemo {
public static void main(String[] args) {
try {
// Creating person objects
Person person1 = new Person("John Doe", 30, "john@email.com");
Person person2 = new Person("Jane Smith", 25, "jane@email.com");

System.out.println("=== Initial State ===");
person1.displayInfo();
System.out.println();
person2.displayInfo();

System.out.println("\n=== Employment Operations ===");
// Hiring employees
person1.hire(50000.0);
person2.hire(45000.0);

System.out.println();
person1.displayInfo();
System.out.println();
person2.displayInfo();

System.out.println("\n=== Giving Raises ===");
person1.giveRaise(10.0); // 10% raise
person2.giveRaise(7.5); // 7.5% raise

System.out.println("\n=== Trying to Set Invalid Data ===");
// These will throw exceptions due to validation in setters
try {
person1.setAge(-5); // Invalid age
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}

try {
person2.setEmail("invalid-email"); // Invalid email
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}

System.out.println("\n=== Final State ===");
person1.displayInfo();
System.out.println();
person2.displayInfo();

} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Inheritance

Inheritance is a mechanism that allows a new class to inherit properties and methods from an existing class. It promotes code reuse and establishes a natural hierarchy.

Key Terms

  • Parent Class/Superclass/Base Class: The class being inherited from
  • Child Class/Subclass/Derived Class: The class that inherits
  • extends: Keyword used to establish inheritance relationship

Basic Inheritance Example

// Parent class (Superclass)
public class Animal {
// Protected - accessible by subclasses
protected String name;
protected int age;
protected String species;

// Constructor
public Animal(String name, int age, String species) {
this.name = name;
this.age = age;
this.species = species;
System.out.println("Animal constructor called");
}

// Methods that can be inherited
public void eat() {
System.out.println(name + " is eating");
}

public void sleep() {
System.out.println(name + " is sleeping");
}

public void makeSound() {
System.out.println(name + " makes a sound");
}

public void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Species: " + species);
}

// Getters
public String getName() { return name; }
public int getAge() { return age; }
public String getSpecies() { return species; }
}

// Child class 1
public class Dog extends Animal {
private String breed;
private boolean isGoodBoy;

public Dog(String name, int age, String breed) {
super(name, age, "Canine"); // Call parent constructor
this.breed = breed;
this.isGoodBoy = true;
System.out.println("Dog constructor called");
}

// Override parent method
@Override
public void makeSound() {
System.out.println(name + " barks: Woof! Woof!");
}

// New methods specific to Dog
public void wagTail() {
System.out.println(name + " is wagging tail happily!");
}

public void fetch() {
System.out.println(name + " is fetching the ball!");
}

public void setGoodBoy(boolean isGoodBoy) {
this.isGoodBoy = isGoodBoy;
}

// Override displayInfo to show additional Dog-specific info
@Override
public void displayInfo() {
super.displayInfo(); // Call parent method
System.out.println("Breed: " + breed);
System.out.println("Good Boy: " + (isGoodBoy ? "Yes" : "No"));
}

public String getBreed() { return breed; }
}

// Child class 2
public class Cat extends Animal {
private boolean isIndoor;
private int livesRemaining;

public Cat(String name, int age, boolean isIndoor) {
super(name, age, "Feline"); // Call parent constructor
this.isIndoor = isIndoor;
this.livesRemaining = 9; // Cats have 9 lives!
System.out.println("Cat constructor called");
}

// Override parent method
@Override
public void makeSound() {
System.out.println(name + " meows: Meow! Meow!");
}

// New methods specific to Cat
public void climb() {
System.out.println(name + " is climbing the tree!");
}

public void purr() {
System.out.println(name + " is purring contentedly");
}

public void useLife() {
if (livesRemaining > 0) {
livesRemaining--;
System.out.println(name + " used a life! Lives remaining: " + livesRemaining);
}
}

@Override
public void displayInfo() {
super.displayInfo(); // Call parent method
System.out.println("Indoor Cat: " + (isIndoor ? "Yes" : "No"));
System.out.println("Lives Remaining: " + livesRemaining);
}

public boolean isIndoor() { return isIndoor; }
public int getLivesRemaining() { return livesRemaining; }
}

public class InheritanceDemo {
public static void main(String[] args) {
System.out.println("=== Creating Animals ===\n");

// Creating different animals
Animal genericAnimal = new Animal("Generic", 5, "Unknown");
Dog dog = new Dog("Buddy", 3, "Golden Retriever");
Cat cat = new Cat("Whiskers", 2, true);

System.out.println("\n=== Animal Information ===\n");

// Display all animals
Animal[] animals = {genericAnimal, dog, cat};

for (Animal animal : animals) {
System.out.println("--- " + animal.getClass().getSimpleName() + " ---");
animal.displayInfo();
animal.makeSound(); // Polymorphism in action
animal.eat();
animal.sleep();

// Type-specific behaviors
if (animal instanceof Dog) {
Dog d = (Dog) animal;
d.wagTail();
d.fetch();
} else if (animal instanceof Cat) {
Cat c = (Cat) animal;
c.purr();
c.climb();
}

System.out.println();
}

System.out.println("=== Demonstrating Method Overriding ===\n");
dog.makeSound(); // Calls Dog's version
cat.makeSound(); // Calls Cat's version
genericAnimal.makeSound(); // Calls Animal's version
}
}

Polymorphism

Polymorphism means "many forms". It allows objects of different types to be treated as objects of a common base type, while still maintaining their specific behaviors.

Types of Polymorphism

  1. Compile-time Polymorphism - Method Overloading
  2. Runtime Polymorphism - Method Overriding

Runtime Polymorphism Example

// Base class
public abstract class Shape {
protected String color;
protected boolean filled;

public Shape(String color, boolean filled) {
this.color = color;
this.filled = filled;
}

// Abstract method - must be implemented by subclasses
public abstract double getArea();
public abstract double getPerimeter();
public abstract void draw();

// Concrete method
public void displayInfo() {
System.out.println("Color: " + color);
System.out.println("Filled: " + (filled ? "Yes" : "No"));
System.out.printf("Area: %.2f%n", getArea());
System.out.printf("Perimeter: %.2f%n", getPerimeter());
}

// Getters and setters
public String getColor() { return color; }
public boolean isFilled() { return filled; }
public void setColor(String color) { this.color = color; }
public void setFilled(boolean filled) { this.filled = filled; }
}

// Derived class 1
public class Circle extends Shape {
private double radius;

public Circle(String color, boolean filled, double radius) {
super(color, filled);
this.radius = radius;
}

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

@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}

@Override
public void draw() {
System.out.println("Drawing a " + color + " circle with radius " + radius);
}

@Override
public void displayInfo() {
System.out.println("=== Circle ===");
System.out.println("Radius: " + radius);
super.displayInfo();
}

public double getRadius() { return radius; }
public void setRadius(double radius) { this.radius = radius; }
}

// Derived class 2
public class Rectangle extends Shape {
private double length;
private double width;

public Rectangle(String color, boolean filled, double length, double width) {
super(color, filled);
this.length = length;
this.width = width;
}

@Override
public double getArea() {
return length * width;
}

@Override
public double getPerimeter() {
return 2 * (length + width);
}

@Override
public void draw() {
System.out.println("Drawing a " + color + " rectangle " + length + "x" + width);
}

@Override
public void displayInfo() {
System.out.println("=== Rectangle ===");
System.out.println("Length: " + length);
System.out.println("Width: " + width);
super.displayInfo();
}

public double getLength() { return length; }
public double getWidth() { return width; }
}

// Derived class 3
public class Triangle extends Shape {
private double side1, side2, side3;

public Triangle(String color, boolean filled, double side1, double side2, double side3) {
super(color, filled);
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
}

@Override
public double getArea() {
// Using Heron's formula
double s = getPerimeter() / 2;
return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
}

@Override
public double getPerimeter() {
return side1 + side2 + side3;
}

@Override
public void draw() {
System.out.println("Drawing a " + color + " triangle with sides " +
side1 + ", " + side2 + ", " + side3);
}

@Override
public void displayInfo() {
System.out.println("=== Triangle ===");
System.out.println("Sides: " + side1 + ", " + side2 + ", " + side3);
super.displayInfo();
}

public double getSide1() { return side1; }
public double getSide2() { return side2; }
public double getSide3() { return side3; }
}

// Utility class to demonstrate polymorphism
public class ShapeCalculator {

// Method that works with any Shape (polymorphism)
public static void processShape(Shape shape) {
shape.displayInfo();
shape.draw();
System.out.println("Processing completed.\n");
}

// Method to calculate total area of multiple shapes
public static double calculateTotalArea(Shape[] shapes) {
double totalArea = 0;
System.out.println("=== Calculating Total Area ===");

for (Shape shape : shapes) {
double area = shape.getArea(); // Polymorphic method call
System.out.printf("%s area: %.2f%n",
shape.getClass().getSimpleName(), area);
totalArea += area;
}

return totalArea;
}

// Method to find the largest shape by area
public static Shape findLargestShape(Shape[] shapes) {
if (shapes == null || shapes.length == 0) return null;

Shape largest = shapes[0];
for (Shape shape : shapes) {
if (shape.getArea() > largest.getArea()) {
largest = shape;
}
}
return largest;
}
}

public class PolymorphismDemo {
public static void main(String[] args) {
System.out.println("=== Polymorphism Demo ===\n");

// Creating different shapes
Shape[] shapes = {
new Circle("Red", true, 5.0),
new Rectangle("Blue", false, 4.0, 6.0),
new Triangle("Green", true, 3.0, 4.0, 5.0),
new Circle("Yellow", true, 3.0),
new Rectangle("Purple", true, 2.5, 8.0)
};

// Polymorphism in action - same method works with all shapes
System.out.println("=== Processing All Shapes ===\n");
for (Shape shape : shapes) {
ShapeCalculator.processShape(shape); // Polymorphic method call
}

// Calculate total area
double totalArea = ShapeCalculator.calculateTotalArea(shapes);
System.out.printf("Total area of all shapes: %.2f%n\n", totalArea);

// Find largest shape
Shape largest = ShapeCalculator.findLargestShape(shapes);
if (largest != null) {
System.out.println("=== Largest Shape ===");
largest.displayInfo();
}

// Demonstrating runtime type checking
System.out.println("=== Runtime Type Information ===");
for (int i = 0; i < shapes.length; i++) {
Shape shape = shapes[i];
System.out.printf("Shape %d is a %s%n", i + 1, shape.getClass().getSimpleName());

// Type-specific operations using instanceof
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
System.out.println(" Radius: " + circle.getRadius());
} else if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
System.out.println(" Dimensions: " + rectangle.getLength() +
" x " + rectangle.getWidth());
} else if (shape instanceof Triangle) {
Triangle triangle = (Triangle) shape;
System.out.println(" Sides: " + triangle.getSide1() +
", " + triangle.getSide2() +
", " + triangle.getSide3());
}
}
}
}

Method Overloading (Compile-time Polymorphism)

public class Calculator {

// Method overloading - same name, different parameters
public int add(int a, int b) {
System.out.println("Adding two integers");
return a + b;
}

public double add(double a, double b) {
System.out.println("Adding two doubles");
return a + b;
}

public int add(int a, int b, int c) {
System.out.println("Adding three integers");
return a + b + c;
}

public String add(String a, String b) {
System.out.println("Concatenating two strings");
return a + b;
}

// Variable arguments (varargs)
public int add(int... numbers) {
System.out.println("Adding " + numbers.length + " integers using varargs");
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}

// Different parameter order
public void display(String message, int number) {
System.out.println("Message: " + message + ", Number: " + number);
}

public void display(int number, String message) {
System.out.println("Number: " + number + ", Message: " + message);
}
}

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

System.out.println("=== Method Overloading Demo ===\n");

// Different methods will be called based on arguments
System.out.println("Result: " + calc.add(5, 3)); // int version
System.out.println("Result: " + calc.add(5.5, 3.2)); // double version
System.out.println("Result: " + calc.add(1, 2, 3)); // three int version
System.out.println("Result: " + calc.add("Hello", " World")); // String version
System.out.println("Result: " + calc.add(1, 2, 3, 4, 5)); // varargs version

System.out.println();
calc.display("Test", 100); // String, int version
calc.display(200, "Message"); // int, String version
}
}

Abstraction

Abstraction is the process of hiding implementation details and showing only the functionality to the user. In Java, abstraction is achieved using abstract classes and interfaces.

Abstract Classes

// Abstract class - cannot be instantiated directly
public abstract class Employee {
protected String name;
protected int id;
protected String department;
protected double baseSalary;

// Constructor in abstract class
public Employee(String name, int id, String department, double baseSalary) {
this.name = name;
this.id = id;
this.department = department;
this.baseSalary = baseSalary;
}

// Concrete methods (implemented)
public void displayBasicInfo() {
System.out.println("Name: " + name);
System.out.println("ID: " + id);
System.out.println("Department: " + department);
System.out.println("Base Salary: $" + baseSalary);
}

public void clockIn() {
System.out.println(name + " has clocked in");
}

public void clockOut() {
System.out.println(name + " has clocked out");
}

// Abstract methods (must be implemented by subclasses)
public abstract double calculateSalary();
public abstract void performDuties();
public abstract String getEmployeeType();
public abstract double calculateBonus(double performanceRating);

// Getters
public String getName() { return name; }
public int getId() { return id; }
public String getDepartment() { return department; }
public double getBaseSalary() { return baseSalary; }
}

// Concrete subclass 1
public class FullTimeEmployee extends Employee {
private double benefits;
private int vacationDays;

public FullTimeEmployee(String name, int id, String department,
double baseSalary, double benefits) {
super(name, id, department, baseSalary);
this.benefits = benefits;
this.vacationDays = 20; // Standard vacation days
}

@Override
public double calculateSalary() {
return baseSalary + benefits;
}

@Override
public void performDuties() {
System.out.println(name + " is working full-time duties in " + department);
}

@Override
public String getEmployeeType() {
return "Full-Time Employee";
}

@Override
public double calculateBonus(double performanceRating) {
// Full-time employees get bonus based on performance
if (performanceRating >= 4.0) {
return baseSalary * 0.15; // 15% bonus for excellent performance
} else if (performanceRating >= 3.0) {
return baseSalary * 0.10; // 10% bonus for good performance
} else {
return baseSalary * 0.05; // 5% bonus for satisfactory performance
}
}

public void takeVacation(int days) {
if (days <= vacationDays) {
vacationDays -= days;
System.out.println(name + " took " + days + " vacation days. Remaining: " + vacationDays);
} else {
System.out.println("Not enough vacation days available");
}
}

@Override
public void displayBasicInfo() {
super.displayBasicInfo();
System.out.println("Benefits: $" + benefits);
System.out.println("Vacation Days: " + vacationDays);
System.out.println("Total Salary: $" + calculateSalary());
}
}

// Concrete subclass 2
public class PartTimeEmployee extends Employee {
private int hoursWorked;
private double hourlyRate;

public PartTimeEmployee(String name, int id, String department, double hourlyRate) {
super(name, id, department, 0); // No base salary for part-time
this.hourlyRate = hourlyRate;
this.hoursWorked = 0;
}

@Override
public double calculateSalary() {
return hoursWorked * hourlyRate;
}

@Override
public void performDuties() {
System.out.println(name + " is working part-time duties in " + department);
}

@Override
public String getEmployeeType() {
return "Part-Time Employee";
}

@Override
public double calculateBonus(double performanceRating) {
// Part-time employees get smaller bonuses
if (performanceRating >= 4.0) {
return calculateSalary() * 0.05; // 5% bonus
} else if (performanceRating >= 3.0) {
return calculateSalary() * 0.03; // 3% bonus
} else {
return 0; // No bonus for poor performance
}
}

public void logHours(int hours) {
hoursWorked += hours;
System.out.println(name + " logged " + hours + " hours. Total: " + hoursWorked);
}

@Override
public void displayBasicInfo() {
super.displayBasicInfo();
System.out.println("Hourly Rate: $" + hourlyRate);
System.out.println("Hours Worked: " + hoursWorked);
System.out.println("Total Salary: $" + calculateSalary());
}
}

public class AbstractionDemo {
public static void main(String[] args) {
System.out.println("=== Employee Management System ===\n");

// Create different types of employees
Employee[] employees = {
new FullTimeEmployee("Alice Johnson", 101, "Engineering", 80000, 15000),
new PartTimeEmployee("Bob Smith", 102, "Marketing", 25.0)
};

// Set up some data
((PartTimeEmployee)employees[1]).logHours(120);

System.out.println("=== Employee Information ===\n");

// Polymorphism with abstract class
for (Employee emp : employees) {
System.out.println("--- " + emp.getEmployeeType() + " ---");
emp.displayBasicInfo();
emp.performDuties();

double bonus = emp.calculateBonus(3.8); // Good performance rating
System.out.println("Performance Bonus: $" + bonus);

emp.clockIn();
emp.clockOut();
System.out.println();
}

// Demonstrate abstraction - we can work with Employee type
// without knowing specific implementation details
System.out.println("=== Payroll Summary ===");
double totalPayroll = 0;

for (Employee emp : employees) {
double salary = emp.calculateSalary(); // Abstract method called
totalPayroll += salary;
System.out.printf("%s (%s): $%.2f%n",
emp.getName(), emp.getEmployeeType(), salary);
}

System.out.printf("Total Monthly Payroll: $%.2f%n", totalPayroll);
}
}

Interfaces

Interfaces define a contract that implementing classes must follow. They contain method signatures (and from Java 8+, default and static methods) but no implementation details.

Basic Interface Example

// Interface definition
public interface Drawable {
// Public, static, final by default
String DEFAULT_COLOR = "Black";
int MAX_SIZE = 1000;

// Abstract methods (public abstract by default)
void draw();
void resize(double factor);
void setColor(String color);
String getColor();

// Default method (Java 8+) - provides default implementation
default void display() {
System.out.println("Displaying drawable object with color: " + getColor());
}

// Static method (Java 8+)
static void printInfo() {
System.out.println("Drawable interface - defines drawing contract");
}
}

// Another interface
public interface Movable {
void move(int x, int y);
void rotate(double angle);

default void reset() {
move(0, 0);
rotate(0);
System.out.println("Position and rotation reset");
}
}

// Interface for mathematical operations
public interface Calculable {
double getArea();
double getPerimeter();

default void printCalculations() {
System.out.printf("Area: %.2f, Perimeter: %.2f%n", getArea(), getPerimeter());
}
}

// Class implementing multiple interfaces
public class Rectangle implements Drawable, Movable, Calculable {
private double width, height;
private String color;
private int x, y;
private double rotationAngle;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
this.color = Drawable.DEFAULT_COLOR;
this.x = 0;
this.y = 0;
this.rotationAngle = 0;
}

// Implementing Drawable interface
@Override
public void draw() {
System.out.printf("Drawing a %s rectangle %.1fx%.1f at (%d,%d) rotated %.1f°%n",
color, width, height, x, y, rotationAngle);
}

@Override
public void resize(double factor) {
width *= factor;
height *= factor;
System.out.printf("Rectangle resized by factor %.2f. New size: %.1fx%.1f%n",
factor, width, height);
}

@Override
public void setColor(String color) {
this.color = color;
}

@Override
public String getColor() {
return color;
}

// Implementing Movable interface
@Override
public void move(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Rectangle moved to position (" + x + ", " + y + ")");
}

@Override
public void rotate(double angle) {
this.rotationAngle = angle;
System.out.println("Rectangle rotated to " + angle + " degrees");
}

// Implementing Calculable interface
@Override
public double getArea() {
return width * height;
}

@Override
public double getPerimeter() {
return 2 * (width + height);
}

public double getWidth() { return width; }
public double getHeight() { return height; }
}

public class InterfaceDemo {
public static void main(String[] args) {
System.out.println("=== Interface Implementation Demo ===\n");

// Using static method from interface
Drawable.printInfo();
System.out.println();

// Creating objects
Rectangle rectangle = new Rectangle(4.0, 6.0);
rectangle.setColor("Blue");

System.out.println("=== Drawing Objects ===");
rectangle.draw();

System.out.println("\n=== Using Default Methods ===");
rectangle.display(); // Default method from Drawable

System.out.println("\n=== Calculations ===");
rectangle.printCalculations(); // Default method from Calculable

System.out.println("\n=== Resizing Objects ===");
rectangle.resize(0.8);

System.out.println("\n=== Moving Rectangle ===");
rectangle.move(10, 20);
rectangle.rotate(45.0);
rectangle.draw();

rectangle.reset(); // Default method from Movable
rectangle.draw();

// Polymorphism with interfaces
System.out.println("\n=== Interface Polymorphism ===");
Drawable drawable = rectangle;
Movable movable = rectangle;
Calculable calculable = rectangle;

drawable.draw();
movable.move(50, 30);
System.out.println("Area through Calculable reference: " + calculable.getArea());
}
}

Access Modifiers

Access modifiers control the visibility and accessibility of classes, methods, and variables.

Four Access Modifiers

ModifierSame ClassSame PackageSubclassDifferent Package
private
default (package-private)
protected
public
public class AccessModifierDemo {
// Private - only accessible within this class
private String privateField = "Private field";

// Package-private (default) - accessible within same package
String packageField = "Package field";

// Protected - accessible within package and by subclasses
protected String protectedField = "Protected field";

// Public - accessible from anywhere
public String publicField = "Public field";

// Private method
private void privateMethod() {
System.out.println("Private method called");
}

// Package-private method
void packageMethod() {
System.out.println("Package method called");
}

// Protected method
protected void protectedMethod() {
System.out.println("Protected method called");
}

// Public method
public void publicMethod() {
System.out.println("Public method called");
}

// Public method that can access all members of same class
public void accessAllMembers() {
System.out.println("=== Accessing from Same Class ===");
System.out.println(privateField); // ✓ Accessible
System.out.println(packageField); // ✓ Accessible
System.out.println(protectedField); // ✓ Accessible
System.out.println(publicField); // ✓ Accessible

privateMethod(); // ✓ Accessible
packageMethod(); // ✓ Accessible
protectedMethod(); // ✓ Accessible
publicMethod(); // ✓ Accessible
}
}

// Same package class
class SamePackageClass {
public void testAccess() {
System.out.println("=== Accessing from Same Package Class ===");
AccessModifierDemo demo = new AccessModifierDemo();

// System.out.println(demo.privateField); // ✗ Compilation error
System.out.println(demo.packageField); // ✓ Accessible
System.out.println(demo.protectedField); // ✓ Accessible
System.out.println(demo.publicField); // ✓ Accessible

// demo.privateMethod(); // ✗ Compilation error
demo.packageMethod(); // ✓ Accessible
demo.protectedMethod(); // ✓ Accessible
demo.publicMethod(); // ✓ Accessible
}
}

Method Overloading and Overriding

Method Overloading vs Method Overriding

AspectOverloadingOverriding
DefinitionMultiple methods with same name, different parametersChild class provides specific implementation of parent method
RelationshipSame classInheritance relationship
Compile TimeResolved at compile timeResolved at runtime
ParametersMust be differentMust be same
Return TypeCan be differentMust be same (or covariant)
Access ModifierCan be differentCannot reduce visibility

Method Overriding Example

// Parent class
public class Vehicle {
protected String brand;
protected String model;
protected int year;

public Vehicle(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
}

// Method to be overridden
public void start() {
System.out.println(brand + " " + model + " is starting...");
}

public void stop() {
System.out.println(brand + " " + model + " has stopped.");
}

// Final method - cannot be overridden
public final void displayInfo() {
System.out.println("Vehicle: " + brand + " " + model + " (" + year + ")");
}

public String getBrand() { return brand; }
public String getModel() { return model; }
public int getYear() { return year; }
}

// Child class demonstrating method overriding
public class ElectricCar extends Vehicle {
private int batteryCapacity;
private int currentCharge;

public ElectricCar(String brand, String model, int year, int batteryCapacity) {
super(brand, model, year);
this.batteryCapacity = batteryCapacity;
this.currentCharge = batteryCapacity;
}

// Method overriding with @Override annotation
@Override
public void start() {
if (currentCharge > 0) {
System.out.println("Electric " + brand + " " + model + " starting silently...");
System.out.println("Battery level: " + currentCharge + "/" + batteryCapacity);
} else {
System.out.println("Cannot start - battery is empty!");
}
}

@Override
public void stop() {
System.out.println("Electric " + brand + " " + model + " stopped. Regenerating energy...");
currentCharge = Math.min(batteryCapacity, currentCharge + 5);
}

public void charge() {
currentCharge = batteryCapacity;
System.out.println("Battery fully charged!");
}
}

public class OverridingDemo {
public static void main(String[] args) {
System.out.println("=== Method Overriding Demo ===\n");

Vehicle[] vehicles = {
new Vehicle("Generic", "Vehicle", 2020),
new ElectricCar("Tesla", "Model 3", 2023, 100)
};

for (Vehicle vehicle : vehicles) {
System.out.println("--- " + vehicle.getClass().getSimpleName() + " ---");
vehicle.displayInfo(); // Final method - same for all
vehicle.start(); // Overridden method - different behavior
vehicle.stop(); // Overridden method
System.out.println();
}
}
}

Super Keyword

The super keyword is used to access parent class members and constructors.

class Animal {
protected String name;
protected int age;

public Animal(String name, int age) {
this.name = name;
this.age = age;
}

public void makeSound() {
System.out.println(name + " makes a sound");
}

public void displayInfo() {
System.out.println("Animal: " + name + ", Age: " + age);
}
}

class Dog extends Animal {
private String breed;

public Dog(String name, int age, String breed) {
super(name, age); // Call parent constructor
this.breed = breed;
}

@Override
public void makeSound() {
super.makeSound(); // Call parent method first
System.out.println(name + " barks: Woof!");
}

@Override
public void displayInfo() {
super.displayInfo(); // Call parent method
System.out.println("Breed: " + breed);
}
}

Final Keyword

The final keyword can be used with variables, methods, and classes.

// Final class - cannot be extended
public final class Constants {
// Final variables - cannot be changed
public static final double PI = 3.14159;
public static final String APP_NAME = "MyApp";

// Final instance variable - must be initialized
private final int id;

public Constants(int id) {
this.id = id; // Can only be set once
}

// Final method - cannot be overridden
public final void displayId() {
System.out.println("ID: " + id);
}
}

Practical Examples

Library Management System

// Abstract base class
abstract class LibraryItem {
protected String title;
protected String author;
protected String id;
protected boolean isAvailable;

public LibraryItem(String title, String author, String id) {
this.title = title;
this.author = author;
this.id = id;
this.isAvailable = true;
}

public abstract void displayInfo();
public abstract double calculateLateFee(int daysLate);

public void checkout() {
if (isAvailable) {
isAvailable = false;
System.out.println(title + " has been checked out");
} else {
System.out.println(title + " is not available");
}
}

public void returnItem() {
isAvailable = true;
System.out.println(title + " has been returned");
}

// Getters
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getId() { return id; }
public boolean isAvailable() { return isAvailable; }
}

class Book extends LibraryItem {
private int pages;
private String genre;

public Book(String title, String author, String id, int pages, String genre) {
super(title, author, id);
this.pages = pages;
this.genre = genre;
}

@Override
public void displayInfo() {
System.out.println("=== Book ===");
System.out.println("Title: " + title);
System.out.println("Author: " + author);
System.out.println("ID: " + id);
System.out.println("Pages: " + pages);
System.out.println("Genre: " + genre);
System.out.println("Available: " + (isAvailable ? "Yes" : "No"));
}

@Override
public double calculateLateFee(int daysLate) {
return daysLate * 0.50; // $0.50 per day
}
}

class DVD extends LibraryItem {
private int duration; // in minutes
private String rating;

public DVD(String title, String author, String id, int duration, String rating) {
super(title, author, id);
this.duration = duration;
this.rating = rating;
}

@Override
public void displayInfo() {
System.out.println("=== DVD ===");
System.out.println("Title: " + title);
System.out.println("Director: " + author);
System.out.println("ID: " + id);
System.out.println("Duration: " + duration + " minutes");
System.out.println("Rating: " + rating);
System.out.println("Available: " + (isAvailable ? "Yes" : "No"));
}

@Override
public double calculateLateFee(int daysLate) {
return daysLate * 1.00; // $1.00 per day
}
}

class Magazine extends LibraryItem {
private String issueDate;
private int issueNumber;

public Magazine(String title, String author, String id, String issueDate, int issueNumber) {
super(title, author, id);
this.issueDate = issueDate;
this.issueNumber = issueNumber;
}

@Override
public void displayInfo() {
System.out.println("=== Magazine ===");
System.out.println("Title: " + title);
System.out.println("Publisher: " + author);
System.out.println("ID: " + id);
System.out.println("Issue Date: " + issueDate);
System.out.println("Issue Number: " + issueNumber);
System.out.println("Available: " + (isAvailable ? "Yes" : "No"));
}

@Override
public double calculateLateFee(int daysLate) {
return daysLate * 0.25; // $0.25 per day
}
}

// Library class to manage items
class Library {
private LibraryItem[] items;
private int itemCount;
private static final int MAX_ITEMS = 1000;

public Library() {
items = new LibraryItem[MAX_ITEMS];
itemCount = 0;
}

public void addItem(LibraryItem item) {
if (itemCount < MAX_ITEMS) {
items[itemCount] = item;
itemCount++;
System.out.println("Added: " + item.getTitle());
} else {
System.out.println("Library is full!");
}
}

public void displayAllItems() {
System.out.println("\n=== Library Catalog ===");
for (int i = 0; i < itemCount; i++) {
items[i].displayInfo();
System.out.println();
}
}

public LibraryItem findItem(String id) {
for (int i = 0; i < itemCount; i++) {
if (items[i].getId().equals(id)) {
return items[i];
}
}
return null;
}

public void checkoutItem(String id) {
LibraryItem item = findItem(id);
if (item != null) {
item.checkout();
} else {
System.out.println("Item with ID " + id + " not found");
}
}

public void returnItem(String id, int daysLate) {
LibraryItem item = findItem(id);
if (item != null) {
item.returnItem();
if (daysLate > 0) {
double fee = item.calculateLateFee(daysLate);
System.out.printf("Late fee: $%.2f%n", fee);
}
} else {
System.out.println("Item with ID " + id + " not found");
}
}

public void displayAvailableItems() {
System.out.println("\n=== Available Items ===");
boolean hasAvailable = false;
for (int i = 0; i < itemCount; i++) {
if (items[i].isAvailable()) {
System.out.println(items[i].getTitle() + " (ID: " + items[i].getId() + ")");
hasAvailable = true;
}
}
if (!hasAvailable) {
System.out.println("No items available");
}
}
}

public class LibraryManagementDemo {
public static void main(String[] args) {
System.out.println("=== Library Management System ===\n");

Library library = new Library();

// Adding different types of items
library.addItem(new Book("Java: The Complete Reference", "Herbert Schildt", "B001", 1200, "Programming"));
library.addItem(new Book("Clean Code", "Robert Martin", "B002", 464, "Programming"));
library.addItem(new DVD("The Matrix", "Wachowski Sisters", "D001", 136, "R"));
library.addItem(new DVD("Inception", "Christopher Nolan", "D002", 148, "PG-13"));
library.addItem(new Magazine("National Geographic", "National Geographic Society", "M001", "January 2024", 1));
library.addItem(new Magazine("Time", "Time USA", "M002", "February 2024", 2));

// Display all items
library.displayAllItems();

// Display available items
library.displayAvailableItems();

// Checkout some items
System.out.println("\n=== Checkout Operations ===");
library.checkoutItem("B001");
library.checkoutItem("D001");
library.checkoutItem("M001");

// Display available items after checkout
library.displayAvailableItems();

// Return items with late fees
System.out.println("\n=== Return Operations ===");
library.returnItem("B001", 5); // 5 days late
library.returnItem("D001", 3); // 3 days late
library.returnItem("M001", 0); // On time

// Display available items after return
library.displayAvailableItems();
}
}

Online Shopping System

// Interface for payment processing
interface PaymentProcessor {
boolean processPayment(double amount);
String getPaymentMethod();
}

// Concrete payment implementations
class CreditCardPayment implements PaymentProcessor {
private String cardNumber;
private String cardHolder;

public CreditCardPayment(String cardNumber, String cardHolder) {
this.cardNumber = cardNumber;
this.cardHolder = cardHolder;
}

@Override
public boolean processPayment(double amount) {
System.out.printf("Processing credit card payment of $%.2f for %s%n", amount, cardHolder);
System.out.println("Card ending in: " + cardNumber.substring(cardNumber.length() - 4));
return true; // Assume payment successful
}

@Override
public String getPaymentMethod() {
return "Credit Card";
}
}

class PayPalPayment implements PaymentProcessor {
private String email;

public PayPalPayment(String email) {
this.email = email;
}

@Override
public boolean processPayment(double amount) {
System.out.printf("Processing PayPal payment of $%.2f for %s%n", amount, email);
return true; // Assume payment successful
}

@Override
public String getPaymentMethod() {
return "PayPal";
}
}

// Abstract product class
abstract class Product {
protected String name;
protected double price;
protected String category;
protected int stockQuantity;

public Product(String name, double price, String category, int stockQuantity) {
this.name = name;
this.price = price;
this.category = category;
this.stockQuantity = stockQuantity;
}

public abstract void displayProductInfo();
public abstract double calculateShippingCost();

public boolean isInStock() {
return stockQuantity > 0;
}

public void reduceStock(int quantity) {
if (stockQuantity >= quantity) {
stockQuantity -= quantity;
} else {
throw new IllegalArgumentException("Insufficient stock");
}
}

// Getters
public String getName() { return name; }
public double getPrice() { return price; }
public String getCategory() { return category; }
public int getStockQuantity() { return stockQuantity; }
}

class Electronics extends Product {
private int warrantyMonths;
private String brand;

public Electronics(String name, double price, int stockQuantity, int warrantyMonths, String brand) {
super(name, price, "Electronics", stockQuantity);
this.warrantyMonths = warrantyMonths;
this.brand = brand;
}

@Override
public void displayProductInfo() {
System.out.println("=== Electronics ===");
System.out.println("Name: " + name);
System.out.println("Brand: " + brand);
System.out.printf("Price: $%.2f%n", price);
System.out.println("Warranty: " + warrantyMonths + " months");
System.out.println("Stock: " + stockQuantity);
}

@Override
public double calculateShippingCost() {
return price > 100 ? 0.0 : 9.99; // Free shipping for expensive electronics
}
}

class Clothing extends Product {
private String size;
private String color;

public Clothing(String name, double price, int stockQuantity, String size, String color) {
super(name, price, "Clothing", stockQuantity);
this.size = size;
this.color = color;
}

@Override
public void displayProductInfo() {
System.out.println("=== Clothing ===");
System.out.println("Name: " + name);
System.out.println("Size: " + size);
System.out.println("Color: " + color);
System.out.printf("Price: $%.2f%n", price);
System.out.println("Stock: " + stockQuantity);
}

@Override
public double calculateShippingCost() {
return 4.99; // Standard shipping for clothing
}
}

// Shopping cart item
class CartItem {
private Product product;
private int quantity;

public CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}

public double getTotalPrice() {
return product.getPrice() * quantity;
}

public double getShippingCost() {
return product.calculateShippingCost();
}

public Product getProduct() { return product; }
public int getQuantity() { return quantity; }
}

// Shopping cart
class ShoppingCart {
private CartItem[] items;
private int itemCount;
private static final int MAX_ITEMS = 100;

public ShoppingCart() {
items = new CartItem[MAX_ITEMS];
itemCount = 0;
}

public void addItem(Product product, int quantity) {
if (!product.isInStock()) {
System.out.println("Product not in stock: " + product.getName());
return;
}

if (product.getStockQuantity() < quantity) {
System.out.println("Insufficient stock for: " + product.getName());
return;
}

if (itemCount < MAX_ITEMS) {
items[itemCount] = new CartItem(product, quantity);
itemCount++;
System.out.println("Added to cart: " + product.getName() + " (Quantity: " + quantity + ")");
} else {
System.out.println("Cart is full!");
}
}

public double calculateSubtotal() {
double subtotal = 0;
for (int i = 0; i < itemCount; i++) {
subtotal += items[i].getTotalPrice();
}
return subtotal;
}

public double calculateShipping() {
double shipping = 0;
for (int i = 0; i < itemCount; i++) {
shipping += items[i].getShippingCost();
}
return shipping;
}

public double calculateTotal() {
return calculateSubtotal() + calculateShipping();
}

public void displayCart() {
System.out.println("\n=== Shopping Cart ===");
if (itemCount == 0) {
System.out.println("Cart is empty");
return;
}

for (int i = 0; i < itemCount; i++) {
CartItem item = items[i];
System.out.printf("%s - Quantity: %d - Price: $%.2f%n",
item.getProduct().getName(),
item.getQuantity(),
item.getTotalPrice());
}

System.out.printf("Subtotal: $%.2f%n", calculateSubtotal());
System.out.printf("Shipping: $%.2f%n", calculateShipping());
System.out.printf("Total: $%.2f%n", calculateTotal());
}

public boolean checkout(PaymentProcessor paymentProcessor) {
if (itemCount == 0) {
System.out.println("Cannot checkout - cart is empty");
return false;
}

double total = calculateTotal();
System.out.println("\n=== Checkout ===");
System.out.printf("Total amount: $%.2f%n", total);
System.out.println("Payment method: " + paymentProcessor.getPaymentMethod());

boolean paymentSuccess = paymentProcessor.processPayment(total);

if (paymentSuccess) {
// Reduce stock for all items
for (int i = 0; i < itemCount; i++) {
CartItem item = items[i];
item.getProduct().reduceStock(item.getQuantity());
}

System.out.println("Order placed successfully!");
clearCart();
return true;
} else {
System.out.println("Payment failed. Please try again.");
return false;
}
}

private void clearCart() {
itemCount = 0;
System.out.println("Cart cleared.");
}

public int getItemCount() { return itemCount; }
}

public class OnlineShoppingDemo {
public static void main(String[] args) {
System.out.println("=== Online Shopping System ===\n");

// Create products
Product laptop = new Electronics("Gaming Laptop", 1299.99, 5, 24, "Dell");
Product phone = new Electronics("Smartphone", 799.99, 10, 12, "Apple");
Product shirt = new Clothing("Cotton T-Shirt", 29.99, 20, "L", "Blue");
Product jeans = new Clothing("Denim Jeans", 79.99, 15, "32", "Black");

// Display product information
System.out.println("=== Available Products ===");
laptop.displayProductInfo();
System.out.println();
phone.displayProductInfo();
System.out.println();
shirt.displayProductInfo();
System.out.println();
jeans.displayProductInfo();

// Create shopping cart
ShoppingCart cart = new ShoppingCart();

// Add items to cart
System.out.println("\n=== Adding Items to Cart ===");
cart.addItem(laptop, 1);
cart.addItem(shirt, 2);
cart.addItem(jeans, 1);

// Display cart
cart.displayCart();

// Create payment processor
PaymentProcessor creditCard = new CreditCardPayment("1234567890123456", "John Doe");

// Checkout
cart.checkout(creditCard);

// Try to add the same laptop again (should have reduced stock)
System.out.println("\n=== Checking Stock After Purchase ===");
System.out.println("Laptop stock remaining: " + laptop.getStockQuantity());

// Try different payment method
System.out.println("\n=== Another Purchase ===");
cart.addItem(phone, 1);
cart.displayCart();

PaymentProcessor paypal = new PayPalPayment("john.doe@email.com");
cart.checkout(paypal);
}
}

Summary

This comprehensive guide covers all the fundamental concepts of Object-Oriented Programming in Java:

Key Takeaways

  1. Classes and Objects: Classes are blueprints, objects are instances
  2. Constructors: Initialize objects with different parameter combinations
  3. Encapsulation: Use private fields with public getters/setters for data protection
  4. Inheritance: Extend classes to reuse and enhance functionality
  5. Polymorphism: Same interface, different implementations (overloading and overriding)
  6. Abstraction: Hide implementation details using abstract classes and interfaces
  7. Access Modifiers: Control visibility at different levels
  8. Static Members: Belong to the class, shared among all instances

Best Practices

  • Always use appropriate access modifiers
  • Follow naming conventions (CamelCase for classes, camelCase for methods/variables)
  • Use @Override annotation for method overriding
  • Prefer composition over inheritance when appropriate
  • Make classes immutable when possible using final keyword
  • Use interfaces to define contracts
  • Keep methods and classes focused on single responsibilities
  • Document your code with meaningful comments

Design Principles

  • Single Responsibility Principle: Each class should have one reason to change
  • Open/Closed Principle: Open for extension, closed for modification
  • Liskov Substitution Principle: Subclasses should be substitutable for their base classes
  • Interface Segregation Principle: Depend on abstractions, not concretions
  • Dependency Inversion Principle: High-level modules should not depend on low-level modules

This guide provides a solid foundation for understanding and implementing Object-Oriented Programming concepts in Java. Practice these concepts with real-world projects to master OOP principles effectively.