# Object-Oriented Programming (OOP) in Java
Table of Contents
Object-Oriented Programming is a programming paradigm that organizes code into self-contained objects. Each object combines data and the methods that operate on that data, resulting in more modular, flexible, and reusable code.
1. Core OOP Concepts
Classes and Objects
A class serves as a blueprint for creating objects, defining their attributes and methods. An object represents an instance of a class, containing both data (attributes) and methods to manipulate that data.
// Class definitionpublic class Car { // Attributes (fields) 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; }
// Methods public void startEngine() { System.out.println(brand + " " + model + " engine started!"); }
public void displayInfo() { System.out.println("Brand: " + brand); System.out.println("Model: " + model); System.out.println("Year: " + year); System.out.println("Price: $" + price); }
// Getters and Setters public String getBrand() { return brand; }
public void setPrice(double price) { if (price > 0) { this.price = price; } }}
// Creating objectspublic class Main { public static void main(String[] args) { // Object creation Car car1 = new Car("Toyota", "Camry", 2024, 25000); Car car2 = new Car("Honda", "Accord", 2024, 27000);
// Using objects car1.startEngine(); car1.displayInfo();
car2.startEngine(); car2.displayInfo(); }}2. Encapsulation
Encapsulation is the bundling of data and related methods within a class, keeping them hidden to protect their integrity. External code accesses them through public methods called getters and setters.
Key Concepts
- Information Hiding: Encapsulation hides internal state and implementation details
- Access Control: Data is available only through defined methods
- Constraining Behavior: Access methods ensure data adheres to specific rules
public class BankAccount { // Private fields - hidden from outside private String accountNumber; private String accountHolder; private double balance;
// Constructor public BankAccount(String accountNumber, String accountHolder, double initialBalance) { this.accountNumber = accountNumber; this.accountHolder = accountHolder; this.balance = initialBalance; }
// Public getter - read-only access public double getBalance() { return balance; }
public String getAccountNumber() { return accountNumber; }
// Public methods with validation public void deposit(double amount) { if (amount > 0) { balance += amount; System.out.println("Deposited: $" + amount); } else { System.out.println("Invalid deposit amount"); } }
public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; System.out.println("Withdrawn: $" + amount); } else { System.out.println("Invalid withdrawal amount or insufficient funds"); } }
// Private helper method - internal use only private void logTransaction(String type, double amount) { System.out.println("Transaction: " + type + " - $" + amount); }}
// Usagepublic class EncapsulationDemo { public static void main(String[] args) { BankAccount account = new BankAccount("ACC001", "John Doe", 1000.0);
// Can't access private fields directly // account.balance = 5000; // Compilation error
// Must use public methods account.deposit(500); account.withdraw(200); System.out.println("Balance: $" + account.getBalance()); }}Benefits of Encapsulation
public class User { private String email; private String password; private int age;
// Getter with validation public String getEmail() { return email; }
// Setter with validation public void setEmail(String email) { if (email != null && email.contains("@")) { this.email = email; } else { throw new IllegalArgumentException("Invalid email format"); } }
public void setAge(int age) { if (age >= 0 && age <= 150) { this.age = age; } else { throw new IllegalArgumentException("Invalid age"); } }
// Read-only property (no setter) public String getPassword() { return "********"; // Never expose actual password }
public void setPassword(String password) { if (password != null && password.length() >= 8) { this.password = hashPassword(password); } else { throw new IllegalArgumentException("Password must be at least 8 characters"); } }
private String hashPassword(String password) { // Hash implementation return password; // Simplified }}3. Inheritance
Inheritance allows one class (child/subclass) to acquire properties and behaviors (fields and methods) of another class (parent/superclass). It’s also known as the IS-A relationship.
Types of Inheritance
// 1. Single Inheritanceclass Animal { protected String name;
public Animal(String name) { this.name = name; }
public void eat() { System.out.println(name + " is eating"); }
public void sleep() { System.out.println(name + " is sleeping"); }}
class Dog extends Animal { private String breed;
public Dog(String name, String breed) { super(name); // Call parent constructor this.breed = breed; }
public void bark() { System.out.println(name + " is barking: Woof! Woof!"); }
// Child has access to parent methods public void displayInfo() { System.out.println("Name: " + name); System.out.println("Breed: " + breed); }}
// 2. Multi-level Inheritanceclass Mammal extends Animal { protected int gestationPeriod;
public Mammal(String name, int gestationPeriod) { super(name); this.gestationPeriod = gestationPeriod; }
public void giveBirth() { System.out.println(name + " gives birth to live young"); }}
class Cat extends Mammal { private String color;
public Cat(String name, int gestationPeriod, String color) { super(name, gestationPeriod); this.color = color; }
public void meow() { System.out.println(name + " says: Meow!"); }}
// 3. Hierarchical Inheritanceclass Bird extends Animal { public Bird(String name) { super(name); }
public void fly() { System.out.println(name + " is flying"); }}
class Parrot extends Bird { public Parrot(String name) { super(name); }
public void speak() { System.out.println(name + " is talking"); }}
class Eagle extends Bird { public Eagle(String name) { super(name); }
public void hunt() { System.out.println(name + " is hunting"); }}Multiple Inheritance (Not Supported in Classes)
// Multiple Inheritance is NOT supported for classes in Java// This will cause a compilation error if uncommented:/*class Father { public void money() { System.out.println("Father's money"); }}
class Mother { public void money() { System.out.println("Mother's money"); }}
// Diamond Problem - Which money() method should Child inherit?class Child extends Father, Mother { // ERROR: Cannot extend multiple classes}*/
// However, Multiple Inheritance IS supported through interfacesinterface Flyable { void fly();}
interface Swimmable { void swim();}
class Duck extends Animal implements Flyable, Swimmable { public Duck(String name) { super(name); }
@Override public void fly() { System.out.println(name + " is flying"); }
@Override public void swim() { System.out.println(name + " is swimming"); }}Advantages and Limitations of Inheritance
// Advantages: Code Reusabilityclass Employee { protected String name; protected String employeeId; protected double salary;
public Employee(String name, String employeeId, double salary) { this.name = name; this.employeeId = employeeId; this.salary = salary; }
// Common method for all employees public void calculatePayroll() { System.out.println("Calculating payroll for: " + name); }
public void displayInfo() { System.out.println("Name: " + name); System.out.println("ID: " + employeeId); System.out.println("Salary: $" + salary); }}
class Manager extends Employee { private double bonus;
public Manager(String name, String employeeId, double salary, double bonus) { super(name, employeeId, salary); this.bonus = bonus; }
// Manager-specific method public void approveLeave() { System.out.println(name + " is approving leave requests"); }
@Override public void calculatePayroll() { super.calculatePayroll(); System.out.println("Adding bonus: $" + bonus); }}
class Developer extends Employee { private String programmingLanguage;
public Developer(String name, String employeeId, double salary, String language) { super(name, employeeId, salary); this.programmingLanguage = language; }
// Developer-specific method public void writeCode() { System.out.println(name + " is writing " + programmingLanguage + " code"); }}
// Limitation: Tight coupling between parent and child4. Polymorphism
Polymorphism allows objects of different types to be treated as if they belong to the same type through a shared interface. It abstracts method details, promoting code flexibility and reusability.
Compile-Time Polymorphism (Method Overloading)
Multiple methods in the same class can have the same name but different parameters.
public class Calculator { // Method overloading - same name, different parameters
// Two integers public int add(int a, int b) { return a + b; }
// Three integers public int add(int a, int b, int c) { return a + b + c; }
// Two doubles public double add(double a, double b) { return a + b; }
// Array of integers public int add(int[] numbers) { int sum = 0; for (int num : numbers) { sum += num; } return sum; }}
// Usagepublic class OverloadingDemo { public static void main(String[] args) { Calculator calc = new Calculator();
System.out.println(calc.add(5, 3)); // Output: 8 System.out.println(calc.add(5, 3, 2)); // Output: 10 System.out.println(calc.add(5.5, 3.2)); // Output: 8.7 System.out.println(calc.add(new int[]{1, 2, 3, 4})); // Output: 10 }}Runtime Polymorphism (Method Overriding)
A subclass provides a specific implementation of a method defined in its parent class.
class Animal { public void sound() { System.out.println("Some generic animal sound"); }
public void move() { System.out.println("Animal is moving"); }}
class Dog extends Animal { @Override public void sound() { System.out.println("Bark! Bark!"); }
@Override public void move() { System.out.println("Dog is running"); }
// Dog-specific method public void wagTail() { System.out.println("Dog is wagging tail"); }}
class Cat extends Animal { @Override public void sound() { System.out.println("Meow! Meow!"); }
@Override public void move() { System.out.println("Cat is walking gracefully"); }}
class Bird extends Animal { @Override public void sound() { System.out.println("Chirp! Chirp!"); }
@Override public void move() { System.out.println("Bird is flying"); }}
// Polymorphism in actionpublic class PolymorphismDemo { public static void main(String[] args) { // Parent reference, child objects Animal animal1 = new Dog(); Animal animal2 = new Cat(); Animal animal3 = new Bird();
// Same method call, different behavior animal1.sound(); // Output: Bark! Bark! animal2.sound(); // Output: Meow! Meow! animal3.sound(); // Output: Chirp! Chirp!
animal1.move(); // Output: Dog is running animal2.move(); // Output: Cat is walking gracefully animal3.move(); // Output: Bird is flying
// Array of animals - polymorphic behavior Animal[] animals = {new Dog(), new Cat(), new Bird()}; for (Animal animal : animals) { animal.sound(); animal.move(); System.out.println("---"); } }}Polymorphism with Interfaces
interface Payment { void processPayment(double amount); boolean validatePayment();}
class CreditCardPayment implements Payment { private String cardNumber;
public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; }
@Override public void processPayment(double amount) { System.out.println("Processing credit card payment: $" + amount); System.out.println("Card: " + maskCardNumber()); }
@Override public boolean validatePayment() { return cardNumber != null && cardNumber.length() == 16; }
private String maskCardNumber() { return "**** **** **** " + cardNumber.substring(12); }}
class PayPalPayment implements Payment { private String email;
public PayPalPayment(String email) { this.email = email; }
@Override public void processPayment(double amount) { System.out.println("Processing PayPal payment: $" + amount); System.out.println("Account: " + email); }
@Override public boolean validatePayment() { return email != null && email.contains("@"); }}
class CryptoPayment implements Payment { private String walletAddress;
public CryptoPayment(String walletAddress) { this.walletAddress = walletAddress; }
@Override public void processPayment(double amount) { System.out.println("Processing crypto payment: $" + amount); System.out.println("Wallet: " + walletAddress); }
@Override public boolean validatePayment() { return walletAddress != null && walletAddress.length() >= 26; }}
// Usagepublic class PaymentDemo { public static void main(String[] args) { Payment[] payments = { new CreditCardPayment("1234567890123456"), new PayPalPayment("user@example.com"), new CryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa") };
for (Payment payment : payments) { if (payment.validatePayment()) { payment.processPayment(100.00); } System.out.println("---"); } }}5. Abstraction
Abstraction is about creating a simple interface that exposes only relevant and necessary functionalities, hiding implementation details.
Abstract Classes
// Abstract class - cannot be instantiatedabstract class Shape { protected String color;
public Shape(String color) { this.color = color; }
// Abstract method - must be implemented by subclasses abstract double calculateArea(); abstract double calculatePerimeter();
// Concrete method - shared by all subclasses public void displayColor() { System.out.println("Color: " + color); }
// Concrete method with implementation public void display() { System.out.println("Shape Details:"); displayColor(); System.out.println("Area: " + calculateArea()); System.out.println("Perimeter: " + calculatePerimeter()); }}
class Circle extends Shape { private double radius;
public Circle(String color, double radius) { super(color); this.radius = radius; }
@Override double calculateArea() { return Math.PI * radius * radius; }
@Override double calculatePerimeter() { return 2 * Math.PI * radius; }}
class Rectangle extends Shape { private double length; private double width;
public Rectangle(String color, double length, double width) { super(color); this.length = length; this.width = width; }
@Override double calculateArea() { return length * width; }
@Override double calculatePerimeter() { return 2 * (length + width); }}
class Triangle extends Shape { private double side1, side2, side3;
public Triangle(String color, double side1, double side2, double side3) { super(color); this.side1 = side1; this.side2 = side2; this.side3 = side3; }
@Override double calculateArea() { double s = (side1 + side2 + side3) / 2; return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3)); }
@Override double calculatePerimeter() { return side1 + side2 + side3; }}Interfaces
// Interface - defines a contractinterface Drawable { void draw(); void resize(double factor);}
interface Colorable { void setColor(String color); String getColor();}
// Multiple interface implementationclass Canvas implements Drawable, Colorable { private String color; private int width; private int height;
public Canvas(int width, int height, String color) { this.width = width; this.height = height; this.color = color; }
@Override public void draw() { System.out.println("Drawing canvas: " + width + "x" + height); }
@Override public void resize(double factor) { width *= factor; height *= factor; System.out.println("Resized to: " + width + "x" + height); }
@Override public void setColor(String color) { this.color = color; }
@Override public String getColor() { return color; }}
// Interface with default methods (Java 8+)interface Vehicle { void start(); void stop();
// Default method default void honk() { System.out.println("Beep! Beep!"); }
// Static method static void displayInfo() { System.out.println("This is a vehicle interface"); }}Abstract Class vs Interface
// Use Abstract Class when:// - You have common code to share// - You have a clear IS-A relationshipabstract class Vehicle { protected String brand; protected int year;
public Vehicle(String brand, int year) { this.brand = brand; this.year = year; }
// Concrete method - shared implementation public void displayInfo() { System.out.println("Brand: " + brand + ", Year: " + year); }
// Abstract method - must be implemented abstract void start();}
// Use Interface when:// - You want to define a contract// - Classes are not related but share behaviorinterface Serializable { void serialize(); void deserialize();}
interface Comparable { int compareTo(Object obj);}
// A class can extend one class but implement multiple interfacesclass Car extends Vehicle implements Serializable, Comparable { private double price;
public Car(String brand, int year, double price) { super(brand, year); this.price = price; }
@Override void start() { System.out.println(brand + " car is starting..."); }
@Override public void serialize() { System.out.println("Serializing car data..."); }
@Override public void deserialize() { System.out.println("Deserializing car data..."); }
@Override public int compareTo(Object obj) { Car other = (Car) obj; return Double.compare(this.price, other.price); }}When to Use What
Use Abstract Class when:
- You have a clear IS-A relationship
- You want to share common code among subclasses
- Example:
Animal→Dog,Cat
Use Interface when:
- You want to define a contract
- Classes are not related but share behavior
- Example:
Comparable,Runnable,Serializable
6. Abstract Methods
A method declared without a body (no implementation) within an abstract class is an abstract method.
abstract class DatabaseConnection { protected String connectionString;
// Abstract methods - no implementation abstract void connect(); abstract void disconnect(); abstract void executeQuery(String query);
// Concrete method public void setConnectionString(String connectionString) { this.connectionString = connectionString; }}
class MySQLConnection extends DatabaseConnection { @Override void connect() { System.out.println("Connecting to MySQL: " + connectionString); }
@Override void disconnect() { System.out.println("Disconnecting from MySQL"); }
@Override void executeQuery(String query) { System.out.println("Executing MySQL query: " + query); }}
class PostgreSQLConnection extends DatabaseConnection { @Override void connect() { System.out.println("Connecting to PostgreSQL: " + connectionString); }
@Override void disconnect() { System.out.println("Disconnecting from PostgreSQL"); }
@Override void executeQuery(String query) { System.out.println("Executing PostgreSQL query: " + query); }}Key Points:
- If a class has an abstract method, the class must be declared abstract
- Abstract classes don’t necessarily need abstract methods
- Abstract classes cannot be instantiated directly
7. Liskov Substitution Principle (LSP)
“Objects of a superclass should be replaceable with objects of its subclasses without breaking the correctness of the program.”
// Good Example - Follows LSPclass Rectangle { protected int width; protected int height;
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int getArea() { return width * height; }}
// Bad Example - Violates LSPclass Square extends Rectangle { @Override public void setWidth(int width) { this.width = width; this.height = width; // Forces height = width }
@Override public void setHeight(int height) { this.height = height; this.width = height; // Forces width = height }}
// This breaks LSPclass LSPDemo { public static void printArea(Rectangle rect) { rect.setWidth(5); rect.setHeight(4); // Expected: 20, but if rect is Square, actual: 16 System.out.println("Area: " + rect.getArea()); }
public static void main(String[] args) { Rectangle rect = new Rectangle(); printArea(rect); // Works correctly: 20
Rectangle square = new Square(); printArea(square); // Breaks: 16 instead of 20 }}
// Better approach - Don't inherit if behavior differs fundamentallyinterface Shape { int getArea();}
class BetterRectangle implements Shape { private int width; private int height;
public BetterRectangle(int width, int height) { this.width = width; this.height = height; }
@Override public int getArea() { return width * height; }}
class BetterSquare implements Shape { private int side;
public BetterSquare(int side) { this.side = side; }
@Override public int getArea() { return side * side; }}8. Cohesion and Coupling
Cohesion
Cohesion refers to how closely the methods and data within a single class are related to one another.
// High Cohesion - Goodclass UserAuthentication { private String username; private String password;
public boolean validateCredentials() { return username != null && password != null; }
public boolean authenticateUser() { // All methods work together for authentication return validateCredentials() && checkDatabase(); }
private boolean checkDatabase() { // Authentication logic return true; }}
// Low Cohesion - Badclass UserManager { // Unrelated responsibilities mixed together public void authenticateUser() { } public void sendEmail() { } public void generateReport() { } public void processPayment() { } public void updateDatabase() { }}
// Better approach - Separate concernsclass UserAuthenticator { public void authenticate() { }}
class EmailService { public void sendEmail() { }}
class ReportGenerator { public void generateReport() { }}
class PaymentProcessor { public void processPayment() { }}Coupling
Coupling describes the degree of interdependence between classes or modules.
// Tight Coupling - Badclass OrderProcessor { private MySQLDatabase database; // Directly depends on specific implementation
public OrderProcessor() { this.database = new MySQLDatabase(); }
public void processOrder(Order order) { database.save(order); }}
// Loose Coupling - Good (using interfaces)interface Database { void save(Order order);}
class MySQLDatabase implements Database { @Override public void save(Order order) { System.out.println("Saving to MySQL"); }}
class PostgreSQLDatabase implements Database { @Override public void save(Order order) { System.out.println("Saving to PostgreSQL"); }}
class BetterOrderProcessor { private Database database; // Depends on interface, not implementation
public BetterOrderProcessor(Database database) { this.database = database; }
public void processOrder(Order order) { database.save(order); }}
// Usage - Easy to switch implementationspublic class CouplingDemo { public static void main(String[] args) { Order order = new Order();
// Can easily switch database implementations BetterOrderProcessor processor1 = new BetterOrderProcessor(new MySQLDatabase()); processor1.processOrder(order);
BetterOrderProcessor processor2 = new BetterOrderProcessor(new PostgreSQLDatabase()); processor2.processOrder(order); }}Design Goals
- High Cohesion is desired - methods and data within a class should be closely related
- Loose Coupling is preferred - classes should have minimal dependencies on each other
Benefits of OOP
- Modularity: Objects are organized into standalone entities with public interfaces
- Reusability: Objects and class hierarchies can be reused across projects
- Extensibility: New features can be added through inheritance and interfaces
- Flexibility: Polymorphism enables objects to adapt behavior based on context
- Maintainability: Encapsulation and separation of concerns make code easier to maintain
- Scalability: Well-designed OOP systems can grow without major restructuring