# 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 definition
public 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 objects
public 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);
}
}
// Usage
public 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 Inheritance
class 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 Inheritance
class 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 Inheritance
class 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 interfaces
interface 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 Reusability
class 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 child

4. 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;
}
}
// Usage
public 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 action
public 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;
}
}
// Usage
public 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 instantiated
abstract 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 contract
interface Drawable {
void draw();
void resize(double factor);
}
interface Colorable {
void setColor(String color);
String getColor();
}
// Multiple interface implementation
class 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 relationship
abstract 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 behavior
interface Serializable {
void serialize();
void deserialize();
}
interface Comparable {
int compareTo(Object obj);
}
// A class can extend one class but implement multiple interfaces
class 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: AnimalDog, 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 LSP
class 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 LSP
class 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 LSP
class 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 fundamentally
interface 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 - Good
class 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 - Bad
class UserManager {
// Unrelated responsibilities mixed together
public void authenticateUser() { }
public void sendEmail() { }
public void generateReport() { }
public void processPayment() { }
public void updateDatabase() { }
}
// Better approach - Separate concerns
class 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 - Bad
class 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 implementations
public 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

  1. Modularity: Objects are organized into standalone entities with public interfaces
  2. Reusability: Objects and class hierarchies can be reused across projects
  3. Extensibility: New features can be added through inheritance and interfaces
  4. Flexibility: Polymorphism enables objects to adapt behavior based on context
  5. Maintainability: Encapsulation and separation of concerns make code easier to maintain
  6. Scalability: Well-designed OOP systems can grow without major restructuring
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts

Comments