Java OOP Basics - Objects & Classes
Master Java Object-Oriented Programming: Learn classes, objects, constructors, methods, and the four pillars of OOP (Encapsulation, Inheritance, Polymorphism, Abstraction) with practical examples.
Objects
Real-world entities
Classes
Blueprints/templates
Encapsulation
Data hiding
Inheritance
Code reusability
1. Introduction to Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around objects rather than functions and logic. Java is a pure object-oriented language that fully supports OOP principles.
Why OOP?
- Modularity: Code organized into objects
- Reusability: Classes can be reused
- Maintainability: Easier to modify and maintain
- Scalability: Easier to scale applications
- Security: Data hiding through encapsulation
- Flexibility: Polymorphism enables flexibility
Core OOP Concepts
- Class: Blueprint for objects
- Object: Instance of a class
- Encapsulation: Bundling data with methods
- Inheritance: Creating new classes from existing ones
- Polymorphism: One interface, multiple implementations
- Abstraction: Hiding implementation details
The OOP Mindset
Think in terms of real-world objects. A Car class describes what a car is (attributes: color, model) and what it can do (methods: drive(), stop()). Each actual car you see on the road is an object of that Car class.
Real-world Analogy
Class = Architectural Blueprint
- Defines structure (rooms, doors, windows)
- Specifies materials and dimensions
- But it's NOT a house itself
Object = Actual House
- Built from the blueprint
- Has actual walls, doors, windows
- Can be painted, furnished, occupied
- Multiple houses can be built from same blueprint
// Class Definition (Blueprint)
class Car {
// Attributes/Properties (State)
String color;
String model;
int year;
// Methods/Behaviors
void startEngine() {
System.out.println("Engine started!");
}
void drive() {
System.out.println("Car is driving...");
}
void displayInfo() {
System.out.println("Model: " + model + ", Color: " + color + ", Year: " + year);
}
}
public class ClassVsObject {
public static void main(String[] args) {
// Creating Objects (Instances) from Class
Car car1 = new Car(); // Object 1
car1.color = "Red";
car1.model = "Toyota Camry";
car1.year = 2023;
Car car2 = new Car(); // Object 2
car2.color = "Blue";
car2.model = "Honda Civic";
car2.year = 2022;
Car car3 = new Car(); // Object 3
car3.color = "Black";
car3.model = "Tesla Model S";
car3.year = 2024;
// Using objects
System.out.println("=== Car Objects ===");
car1.displayInfo();
car1.startEngine();
System.out.println();
car2.displayInfo();
car2.drive();
System.out.println();
car3.displayInfo();
car3.startEngine();
car3.drive();
// Each object is independent
System.out.println("\n=== Object Independence ===");
System.out.println("car1 == car2: " + (car1 == car2)); // false
System.out.println("car1 model: " + car1.model);
System.out.println("car2 model: " + car2.model);
}
}
2. Classes in Java - Complete Guide
A class is a template or blueprint for creating objects. It defines the attributes (data) and methods (functions) that the objects created from the class will have.
// Complete Class Structure Example
public class Student {
// ========== 1. CLASS VARIABLES (Static Fields) ==========
static String schoolName = "Java University";
static int totalStudents = 0;
// ========== 2. INSTANCE VARIABLES (Fields/Attributes) ==========
private String name; // Private for encapsulation
private int age;
private String studentId;
private double gpa;
// ========== 3. CONSTRUCTORS ==========
// Default Constructor
public Student() {
this.name = "Unknown";
this.age = 18;
this.studentId = generateStudentId();
this.gpa = 0.0;
totalStudents++; // Update class variable
}
// Parameterized Constructor
public Student(String name, int age) {
this.name = name;
this.age = age;
this.studentId = generateStudentId();
this.gpa = 0.0;
totalStudents++;
}
// Full Parameterized Constructor
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.studentId = generateStudentId();
this.gpa = gpa;
totalStudents++;
}
// ========== 4. METHODS ==========
// Getter Methods (Accessors)
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getStudentId() {
return studentId;
}
public double getGpa() {
return gpa;
}
// Setter Methods (Mutators)
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
public void setAge(int age) {
if (age >= 0 && age <= 120) {
this.age = age;
}
}
public void setGpa(double gpa) {
if (gpa >= 0.0 && gpa <= 4.0) {
this.gpa = gpa;
}
}
// Business Logic Methods
public void study(String subject) {
System.out.println(name + " is studying " + subject);
gpa = Math.min(4.0, gpa + 0.1); // GPA improves with studying
}
public void takeExam(String examName) {
System.out.println(name + " is taking " + examName + " exam");
}
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";
}
// Static Method (Class Method)
public static void displaySchoolInfo() {
System.out.println("School: " + schoolName);
System.out.println("Total Students: " + totalStudents);
}
// Private Helper Method
private String generateStudentId() {
return "STU" + (1000 + totalStudents);
}
// toString() method for string representation
@Override
public String toString() {
return "Student [Name: " + name + ", Age: " + age +
", ID: " + studentId + ", GPA: " + gpa + "]";
}
// ========== 5. MAIN METHOD (for testing) ==========
public static void main(String[] args) {
System.out.println("=== Testing Student Class ===");
// Create students using different constructors
Student student1 = new Student();
Student student2 = new Student("Alice Johnson", 20);
Student student3 = new Student("Bob Smith", 22, 3.8);
// Display student info
System.out.println("Student 1: " + student1);
System.out.println("Student 2: " + student2);
System.out.println("Student 3: " + student3);
// Use methods
student1.setName("Charlie Brown");
student1.setAge(21);
student1.study("Java Programming");
System.out.println("\nAfter studying:");
System.out.println("Student 1 GPA: " + student1.getGpa());
System.out.println("Student 1 Grade Level: " + student1.getGradeLevel());
// Static method call
System.out.println("\nSchool Information:");
Student.displaySchoolInfo();
// Demonstrate encapsulation
System.out.println("\n=== Encapsulation Demonstration ===");
// This would cause error because 'name' is private:
// student1.name = "Direct Access"; // COMPILE ERROR
// Correct way using setter
student1.setName("Updated Name");
System.out.println("Updated name via setter: " + student1.getName());
}
}
Class Components Explained
| Component | Purpose | Example | Access Level | Best Practices |
|---|---|---|---|---|
| Class Variables (static) | Shared by all objects of the class | static int count; |
Usually private | Use for class-wide data |
| Instance Variables | Unique to each object | private String name; |
Should be private | Use getters/setters for access |
| Constructors | Initialize new objects | public Student() { } |
Usually public | Overload for flexibility |
| Getter Methods | Read private data | public String getName() |
Public | Return copy of mutable objects |
| Setter Methods | Modify private data | public void setName() |
Public | Validate input data |
| Business Methods | Implement object behavior | public void study() |
Public/Private | Single responsibility principle |
| Static Methods | Class-level operations | static void displayInfo() |
Public/Private | Don't use instance variables |
| Private Methods | Internal helper methods | private String generateId() |
Private | Implementation details only |
| toString() | String representation | public String toString() |
Public | Always override |
UML Class Diagram: Student Class
3. Objects in Java - Creation and Usage
An object is an instance of a class. It represents a real-world entity with state (attributes) and behavior (methods).
// Book class for object demonstration
class Book {
private String title;
private String author;
private double price;
private int pages;
// Constructors
public Book() {
this.title = "Unknown";
this.author = "Unknown";
this.price = 0.0;
this.pages = 0;
}
public Book(String title, String author) {
this.title = title;
this.author = author;
this.price = 0.0;
this.pages = 0;
}
public Book(String title, String author, double price, int pages) {
this.title = title;
this.author = author;
this.price = price;
this.pages = pages;
}
// Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public double getPrice() { return price; }
public void setPrice(double price) {
if (price >= 0) this.price = price;
}
public int getPages() { return pages; }
public void setPages(int pages) {
if (pages >= 0) this.pages = pages;
}
// Business methods
public void applyDiscount(double percentage) {
if (percentage > 0 && percentage <= 100) {
price = price * (100 - percentage) / 100;
}
}
public double getPricePerPage() {
if (pages > 0) {
return price / pages;
}
return 0;
}
@Override
public String toString() {
return String.format("Book: %s by %s ($%.2f, %d pages)",
title, author, price, pages);
}
}
public class ObjectCreation {
public static void main(String[] args) {
System.out.println("=== Different Ways to Create Objects ===\n");
// Method 1: Using 'new' keyword (most common)
Book book1 = new Book();
book1.setTitle("Java Programming");
book1.setAuthor("John Doe");
book1.setPrice(49.99);
book1.setPages(500);
// Method 2: Using parameterized constructor
Book book2 = new Book("Python Basics", "Jane Smith", 39.99, 400);
// Method 3: Using constructor with partial parameters
Book book3 = new Book("C++ Essentials", "Mike Johnson");
book3.setPrice(44.99);
book3.setPages(350);
// Method 4: Array of objects
Book[] library = new Book[3];
library[0] = book1;
library[1] = book2;
library[2] = book3;
// Method 5: Anonymous object (one-time use)
System.out.println("Anonymous object price: $" +
new Book("Quick Reference", "Author", 9.99, 100).getPrice());
// Display all books
System.out.println("\n=== Library Collection ===");
for (Book book : library) {
System.out.println(book);
}
// Demonstrate object behavior
System.out.println("\n=== Object Behavior Demonstration ===");
book1.applyDiscount(20);
System.out.println("After 20% discount: " + book1);
System.out.printf("Price per page: $%.4f\n", book1.getPricePerPage());
// Object comparison
System.out.println("\n=== Object Comparison ===");
Book book4 = new Book("Java Programming", "John Doe", 49.99, 500);
Book book5 = book1;
System.out.println("book1 == book4: " + (book1 == book4)); // false (different objects)
System.out.println("book1 == book5: " + (book1 == book5)); // true (same object)
System.out.println("book1.equals(book4): " + book1.equals(book4)); // false (default equals())
// Object null reference
System.out.println("\n=== Null Reference Demonstration ===");
Book nullBook = null;
System.out.println("nullBook is: " + nullBook);
// This would cause NullPointerException:
// nullBook.getTitle(); // UNCOMMENT TO SEE ERROR
// Safe way to handle null
if (nullBook != null) {
System.out.println("Title: " + nullBook.getTitle());
} else {
System.out.println("Book is null!");
}
// Garbage collection hint
System.out.println("\n=== Memory Management ===");
Book tempBook = new Book("Temp Book", "Temp Author", 1.99, 50);
tempBook = null; // Now eligible for garbage collection
System.gc(); // Hint to JVM (not guaranteed to run immediately)
}
}
// Bank Account example for object lifecycle
class BankAccount {
private static int accountCounter = 1000;
private final int accountNumber; // final - can't be changed after initialization
private String accountHolder;
private double balance;
private boolean isActive;
// Static initialization block
static {
System.out.println("BankAccount class loaded. Initializing...");
}
// Instance initialization block (runs before each constructor)
{
System.out.println("Creating a new BankAccount object...");
this.isActive = true;
}
// Constructors
public BankAccount(String accountHolder) {
this.accountNumber = ++accountCounter; // Auto-generated
this.accountHolder = accountHolder;
this.balance = 0.0;
}
public BankAccount(String accountHolder, double initialBalance) {
this.accountNumber = ++accountCounter;
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
// Methods
public void deposit(double amount) {
if (amount > 0 && isActive) {
balance += amount;
System.out.printf("Deposited $%.2f. New balance: $%.2f\n", amount, balance);
}
}
public void withdraw(double amount) {
if (amount > 0 && isActive && amount <= balance) {
balance -= amount;
System.out.printf("Withdrew $%.2f. New balance: $%.2f\n", amount, balance);
}
}
public void transfer(BankAccount recipient, double amount) {
if (amount > 0 && this.isActive && recipient.isActive && amount <= balance) {
this.balance -= amount;
recipient.balance += amount;
System.out.printf("Transferred $%.2f to %s\n", amount, recipient.accountHolder);
}
}
public void deactivate() {
this.isActive = false;
System.out.println("Account deactivated.");
}
public void activate() {
this.isActive = true;
System.out.println("Account activated.");
}
// Getters
public int getAccountNumber() { return accountNumber; }
public String getAccountHolder() { return accountHolder; }
public double getBalance() { return balance; }
public boolean isActive() { return isActive; }
@Override
public String toString() {
return String.format("Account #%d: %s - $%.2f [%s]",
accountNumber, accountHolder, balance,
isActive ? "Active" : "Inactive");
}
// Finalizer (deprecated in Java 9+, shown for demonstration)
@Override
protected void finalize() throws Throwable {
try {
System.out.println("Finalizing account #" + accountNumber);
} finally {
super.finalize();
}
}
}
public class ObjectLifecycle {
public static void main(String[] args) {
System.out.println("=== Object Lifecycle Demonstration ===\n");
// 1. Object Creation
System.out.println("1. Creating BankAccount objects:");
BankAccount account1 = new BankAccount("Alice");
BankAccount account2 = new BankAccount("Bob", 1000.0);
System.out.println("\n2. Initial state:");
System.out.println(account1);
System.out.println(account2);
// 3. Object Usage
System.out.println("\n3. Using objects:");
account1.deposit(500.0);
account1.withdraw(200.0);
account1.transfer(account2, 100.0);
System.out.println("\n4. After transactions:");
System.out.println(account1);
System.out.println(account2);
// 4. Object State Changes
System.out.println("\n5. Changing object state:");
account1.deactivate();
account1.deposit(100.0); // Should fail - account inactive
account1.activate();
account1.deposit(100.0); // Should work now
// 5. Object References
System.out.println("\n6. Object references:");
BankAccount account3 = account1; // Both reference same object
System.out.println("account1: " + account1.getAccountNumber());
System.out.println("account3: " + account3.getAccountNumber());
System.out.println("Same object? " + (account1 == account3));
// 6. Making object eligible for garbage collection
System.out.println("\n7. Making objects eligible for GC:");
account3 = null;
// Suggest garbage collection (for demonstration only)
System.gc();
// Wait a bit to see finalizer output
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n=== Object Lifecycle Summary ===");
System.out.println("1. Creation: new keyword");
System.out.println("2. Initialization: constructors, initialization blocks");
System.out.println("3. Usage: method calls, state changes");
System.out.println("4. Inaccessibility: null reference, out of scope");
System.out.println("5. Garbage Collection: automatic memory reclamation");
}
}
Object Concepts and Characteristics
| Concept | Description | Example | Key Points |
|---|---|---|---|
| Instantiation | Creating an object from a class | Book b = new Book(); |
'new' allocates memory and calls constructor |
| Reference Variable | Variable that holds object reference | Book myBook; |
Stores memory address, not the object itself |
| Object State | Current values of object attributes | b.title = "Java" |
Changed by methods; defines current condition |
| Object Behavior | Methods that define what object can do | b.read() |
Implemented as methods in class |
| Object Identity | Unique identifier for each object | b.hashCode() |
Memory address; unique for each object |
| Garbage Collection | Automatic memory reclamation | b = null; |
JVM automatically frees unused objects |
| Object Lifetime | From creation to garbage collection | Create → Use → Destroy | Managed by JVM; developer creates, JVM destroys |
4. Constructors - Object Initialization
Constructors are special methods used to initialize newly created objects. They have the same name as the class and no return type.
// Employee class demonstrating different constructor types
class Employee {
private int id;
private String name;
private String department;
private double salary;
private String email;
private String phone;
// ========== CONSTRUCTOR TYPES ==========
// 1. Default (No-arg) Constructor
public Employee() {
this.id = 0;
this.name = "Unknown";
this.department = "Unassigned";
this.salary = 0.0;
this.email = "no-email@company.com";
this.phone = "000-000-0000";
System.out.println("Default constructor called");
}
// 2. Parameterized Constructor
public Employee(int id, String name, String department, double salary) {
this.id = id;
this.name = name;
this.department = department;
this.salary = salary;
this.email = name.toLowerCase().replace(" ", ".") + "@company.com";
this.phone = "000-000-0000";
System.out.println("Parameterized constructor called");
}
// 3. Constructor Overloading (different parameter list)
public Employee(String name, String department) {
this(0, name, department, 0.0); // Calling another constructor
System.out.println("Two-parameter constructor called");
}
// 4. Copy Constructor (creates copy of another object)
public Employee(Employee other) {
this.id = other.id;
this.name = other.name;
this.department = other.department;
this.salary = other.salary;
this.email = other.email;
this.phone = other.phone;
System.out.println("Copy constructor called");
}
// 5. Constructor with Object Parameter
public Employee(int id, String name, Department dept) {
this.id = id;
this.name = name;
this.department = dept.getName();
this.salary = dept.getBaseSalary();
this.email = generateEmail(name);
this.phone = "000-000-0000";
}
// ========== HELPER METHODS ==========
private String generateEmail(String name) {
return name.toLowerCase().replace(" ", ".") + "@company.com";
}
// ========== GETTERS AND SETTERS ==========
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) {
this.name = name;
this.email = generateEmail(name);
}
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
// ========== BUSINESS METHODS ==========
public void giveRaise(double percentage) {
if (percentage > 0) {
salary += salary * percentage / 100;
System.out.printf("%s got %.1f%% raise. New salary: $%.2f\n",
name, percentage, salary);
}
}
public void transferDepartment(String newDept) {
System.out.printf("%s transferred from %s to %s\n",
name, department, newDept);
department = newDept;
}
@Override
public String toString() {
return String.format("Employee #%d: %s (%s) - $%.2f",
id, name, department, salary);
}
}
// Supporting Department class
class Department {
private String name;
private double baseSalary;
public Department(String name, double baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public String getName() { return name; }
public double getBaseSalary() { return baseSalary; }
}
public class ConstructorTypes {
public static void main(String[] args) {
System.out.println("=== Constructor Types Demonstration ===\n");
// 1. Using default constructor
System.out.println("1. Default Constructor:");
Employee emp1 = new Employee();
System.out.println(emp1);
// 2. Using parameterized constructor
System.out.println("\n2. Parameterized Constructor:");
Employee emp2 = new Employee(101, "John Doe", "Engineering", 75000.0);
System.out.println(emp2);
System.out.println("Email: " + emp2.getEmail());
// 3. Using overloaded constructor
System.out.println("\n3. Overloaded Constructor:");
Employee emp3 = new Employee("Jane Smith", "Marketing");
System.out.println(emp3);
// 4. Using copy constructor
System.out.println("\n4. Copy Constructor:");
Employee emp4 = new Employee(emp2); // Copy of emp2
emp4.setId(102); // Change ID to make it different
emp4.setName("John Doe Clone");
System.out.println("Original: " + emp2);
System.out.println("Copy: " + emp4);
// 5. Constructor with object parameter
System.out.println("\n5. Constructor with Object Parameter:");
Department itDept = new Department("IT", 80000.0);
Employee emp5 = new Employee(103, "Bob Johnson", itDept);
System.out.println(emp5);
// 6. Constructor chaining demonstration
System.out.println("\n6. Constructor Chaining:");
System.out.println("Note how constructors call each other using this()");
// 7. Object initialization sequence
System.out.println("\n7. Initialization Sequence:");
System.out.println("1. Default values assigned");
System.out.println("2. Instance initialization blocks executed");
System.out.println("3. Constructor executed");
// 8. Common constructor mistakes
System.out.println("\n8. Common Constructor Mistakes to Avoid:");
System.out.println("- Forgetting to initialize all fields");
System.out.println("- Not validating input parameters");
System.out.println("- Not calling super() in inheritance hierarchy");
System.out.println("- Making constructors too complex");
// 9. Best practices
System.out.println("\n9. Constructor Best Practices:");
System.out.println("- Keep constructors simple");
System.out.println("- Overload constructors for flexibility");
System.out.println("- Use copy constructors for immutable objects");
System.out.println("- Consider using factory methods for complex creation");
System.out.println("- Always validate parameters in public constructors");
}
}
Constructor Rules & Features
- Name: Same as class name
- Return Type: No return type (not even void)
- Automatic: Java provides default constructor if none defined
- Overloading: Multiple constructors with different parameters
- Chaining: One constructor can call another using
this() - Inheritance: Subclass constructors must call superclass constructor
- Access Modifiers: Can be public, protected, private, or default
Constructor Common Issues
- Defining return type makes it a method, not constructor
- Forgetting to initialize all instance variables
- Not calling super() in subclass constructors
- Complex logic in constructors (should be simple)
- Circular constructor calls (infinite recursion)
- Throwing exceptions without proper handling
// Demonstrating constructor chaining
class Product {
private String name;
private double price;
private int quantity;
private String category;
private String sku;
// Instance initialization block
{
System.out.println("Instance initialization block executed");
category = "Uncategorized";
}
// Constructor 1: Most detailed
public Product(String name, double price, int quantity, String category, String sku) {
System.out.println("5-parameter constructor called");
this.name = name;
this.price = price;
this.quantity = quantity;
this.category = category;
this.sku = sku;
}
// Constructor 2: Chains to constructor 1
public Product(String name, double price, int quantity) {
this(name, price, quantity, "General", "SKU-" + System.currentTimeMillis());
System.out.println("3-parameter constructor called");
}
// Constructor 3: Chains to constructor 2
public Product(String name, double price) {
this(name, price, 1); // Default quantity = 1
System.out.println("2-parameter constructor called");
}
// Constructor 4: Default constructor chains to constructor 3
public Product() {
this("Unknown Product", 0.0);
System.out.println("Default constructor called");
}
// Copy constructor
public Product(Product other) {
this(other.name, other.price, other.quantity, other.category, other.sku);
System.out.println("Copy constructor called");
}
@Override
public String toString() {
return String.format("%s - $%.2f (Qty: %d, Cat: %s)", name, price, quantity, category);
}
}
public class ConstructorChaining {
public static void main(String[] args) {
System.out.println("=== Constructor Chaining Demonstration ===\n");
System.out.println("Creating Product 1 (Default constructor):");
Product p1 = new Product();
System.out.println(p1);
System.out.println("\nCreating Product 2 (2-parameter constructor):");
Product p2 = new Product("Laptop", 999.99);
System.out.println(p2);
System.out.println("\nCreating Product 3 (3-parameter constructor):");
Product p3 = new Product("Mouse", 25.99, 10);
System.out.println(p3);
System.out.println("\nCreating Product 4 (5-parameter constructor):");
Product p4 = new Product("Keyboard", 79.99, 5, "Electronics", "KB-12345");
System.out.println(p4);
System.out.println("\nCreating Product 5 (Copy constructor):");
Product p5 = new Product(p4);
System.out.println(p5);
System.out.println("\n=== Constructor Chaining Flow ===");
System.out.println("When calling new Product(\"Test\", 10.0):");
System.out.println("1. Default values assigned");
System.out.println("2. Instance init block executed");
System.out.println("3. 2-param constructor starts");
System.out.println("4. Calls 3-param constructor via this()");
System.out.println("5. 3-param constructor calls 5-param constructor");
System.out.println("6. 5-param constructor executes body");
System.out.println("7. Returns to 3-param constructor body");
System.out.println("8. Returns to 2-param constructor body");
System.out.println("9. Object created and returned");
}
}
5. Methods - Object Behavior
Methods define the behavior of objects. They are functions that belong to a class and operate on object data.
// Calculator class demonstrating different method types
class Calculator {
// ========== INSTANCE VARIABLES ==========
private double lastResult;
private int operationCount;
private String model;
// ========== CONSTRUCTOR ==========
public Calculator(String model) {
this.model = model;
this.lastResult = 0.0;
this.operationCount = 0;
}
// ========== INSTANCE METHODS ==========
// 1. Accessor Methods (Getters)
public double getLastResult() {
return lastResult;
}
public int getOperationCount() {
return operationCount;
}
public String getModel() {
return model;
}
// 2. Mutator Methods (Setters)
public void reset() {
lastResult = 0.0;
operationCount = 0;
System.out.println("Calculator reset");
}
// 3. Business Logic Methods
public double add(double a, double b) {
operationCount++;
lastResult = a + b;
return lastResult;
}
public double subtract(double a, double b) {
operationCount++;
lastResult = a - b;
return lastResult;
}
public double multiply(double a, double b) {
operationCount++;
lastResult = a * b;
return lastResult;
}
public double divide(double a, double b) {
if (b != 0) {
operationCount++;
lastResult = a / b;
return lastResult;
} else {
System.out.println("Error: Division by zero!");
return Double.NaN;
}
}
// Method overloading (same name, different parameters)
public double add(double a, double b, double c) {
return add(add(a, b), c); // Reuse existing method
}
// Method with variable arguments (varargs)
public double sum(double... numbers) {
double total = 0.0;
for (double num : numbers) {
total += num;
}
operationCount++;
lastResult = total;
return total;
}
// Method returning complex object
public CalculationResult calculateWithDetails(double a, double b, String operation) {
double result = 0.0;
switch (operation.toLowerCase()) {
case "add": result = add(a, b); break;
case "subtract": result = subtract(a, b); break;
case "multiply": result = multiply(a, b); break;
case "divide": result = divide(a, b); break;
default: result = Double.NaN;
}
return new CalculationResult(a, b, operation, result);
}
// Recursive method (calls itself)
public double power(double base, int exponent) {
if (exponent == 0) return 1.0;
if (exponent < 0) return 1.0 / power(base, -exponent);
operationCount++;
lastResult = base * power(base, exponent - 1);
return lastResult;
}
// Method with object parameter
public void updateModel(CalculatorConfig config) {
if (config != null) {
this.model = config.getModelName();
System.out.println("Model updated to: " + model);
}
}
// Method that throws exception
public double safeDivide(double a, double b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("Division by zero is not allowed");
}
return divide(a, b);
}
// ========== STATIC METHODS ==========
public static String getCalculatorInfo() {
return "Basic Calculator with arithmetic operations";
}
public static double convertToDecimal(String binary) {
return Integer.parseInt(binary, 2);
}
// Factory method (static method that creates objects)
public static Calculator createScientificCalculator() {
Calculator calc = new Calculator("Scientific-X100");
return calc;
}
// ========== PRIVATE HELPER METHODS ==========
private void incrementOperationCount() {
operationCount++;
}
private boolean isValidNumber(double num) {
return !Double.isNaN(num) && !Double.isInfinite(num);
}
// ========== FINAL METHOD (cannot be overridden) ==========
public final String getManufacturer() {
return "JavaCalc Inc.";
}
// ========== toString METHOD ==========
@Override
public String toString() {
return String.format("Calculator [Model: %s, Last Result: %.2f, Operations: %d]",
model, lastResult, operationCount);
}
}
// Supporting classes
class CalculationResult {
private double operand1;
private double operand2;
private String operation;
private double result;
public CalculationResult(double op1, double op2, String op, double res) {
this.operand1 = op1;
this.operand2 = op2;
this.operation = op;
this.result = res;
}
@Override
public String toString() {
return String.format("%.2f %s %.2f = %.2f", operand1, operation, operand2, result);
}
}
class CalculatorConfig {
private String modelName;
private boolean scientific;
public CalculatorConfig(String modelName, boolean scientific) {
this.modelName = modelName;
this.scientific = scientific;
}
public String getModelName() { return modelName; }
public boolean isScientific() { return scientific; }
}
public class MethodTypes {
public static void main(String[] args) {
System.out.println("=== Method Types Demonstration ===\n");
// Create calculator
Calculator calc = new Calculator("Basic-2000");
System.out.println("Created: " + calc);
// Instance method calls
System.out.println("\n=== Instance Methods ===");
System.out.println("5 + 3 = " + calc.add(5, 3));
System.out.println("10 - 4 = " + calc.subtract(10, 4));
System.out.println("6 * 7 = " + calc.multiply(6, 7));
System.out.println("15 / 3 = " + calc.divide(15, 3));
// Method overloading
System.out.println("\n=== Method Overloading ===");
System.out.println("Sum of 1, 2, 3 = " + calc.add(1, 2, 3));
// Varargs method
System.out.println("\n=== Variable Arguments ===");
System.out.println("Sum of 1, 2, 3, 4, 5 = " + calc.sum(1, 2, 3, 4, 5));
// Method returning object
System.out.println("\n=== Method Returning Object ===");
CalculationResult result = calc.calculateWithDetails(10, 2, "divide");
System.out.println("Calculation Result: " + result);
// Recursive method
System.out.println("\n=== Recursive Method ===");
System.out.println("2^5 = " + calc.power(2, 5));
// Static method call
System.out.println("\n=== Static Methods ===");
System.out.println("Calculator Info: " + Calculator.getCalculatorInfo());
System.out.println("Binary 1010 to decimal: " + Calculator.convertToDecimal("1010"));
// Factory method
System.out.println("\n=== Factory Method ===");
Calculator sciCalc = Calculator.createScientificCalculator();
System.out.println("Created: " + sciCalc);
// Method with exception
System.out.println("\n=== Method with Exception ===");
try {
System.out.println("10 / 0 = " + calc.safeDivide(10, 0));
} catch (ArithmeticException e) {
System.out.println("Caught exception: " + e.getMessage());
}
// Final method
System.out.println("\n=== Final Method ===");
System.out.println("Manufacturer: " + calc.getManufacturer());
// Method summary
System.out.println("\n=== Method Summary ===");
System.out.println("Total operations performed: " + calc.getOperationCount());
System.out.println("Last result: " + calc.getLastResult());
// Reset and final state
calc.reset();
System.out.println("\nAfter reset: " + calc);
}
}
Method Types and Characteristics
| Method Type | Purpose | Example | Key Features |
|---|---|---|---|
| Instance Methods | Operate on object instance | public void drive() |
Access instance variables, use 'this' keyword |
| Static Methods | Class-level operations | static int getCount() |
Cannot access instance variables, called via class name |
| Getter Methods | Return private field values | public String getName() |
Follow naming convention: getFieldName() |
| Setter Methods | Modify private field values | public void setName() |
Validate input, follow naming convention |
| Factory Methods | Create and return objects | static Car createSportsCar() |
Often static, provide controlled object creation |
| Overloaded Methods | Same name, different parameters | add(int a, int b)add(double a, double b) |
Compile-time polymorphism, different signatures |
| Recursive Methods | Call themselves | factorial(int n) |
Need base case, can cause stack overflow |
| Final Methods | Cannot be overridden | public final void process() |
Used for security, performance, or design |
| Abstract Methods | No implementation, must be overridden | abstract void draw() |
Only in abstract classes/interfaces |
6. The Four Pillars of OOP
1. Encapsulation
Bundling data with methods that operate on that data.
- Hide internal state
- Control access via getters/setters
- Maintain data integrity
- Reduce complexity
// Bank Account with proper encapsulation
class BankAccount {
// Private fields (encapsulated)
private String accountNumber;
private String accountHolder;
private double balance;
private String password;
// Constructor
public BankAccount(String accountHolder, String password) {
this.accountNumber = "ACC" + System.currentTimeMillis();
this.accountHolder = accountHolder;
this.balance = 0.0;
setPassword(password); // Use setter for validation
}
// Public getters (controlled access)
public String getAccountNumber() { return accountNumber; }
public String getAccountHolder() { return accountHolder; }
public double getBalance() { return balance; }
// No direct getter for password (security)
// Public setters with validation
public void setAccountHolder(String accountHolder) {
if (accountHolder != null && !accountHolder.trim().isEmpty()) {
this.accountHolder = accountHolder;
}
}
private void setPassword(String password) {
if (password != null && password.length() >= 8) {
this.password = password;
} else {
throw new IllegalArgumentException("Password must be at least 8 characters");
}
}
// Public methods for operations (business logic)
public boolean deposit(double amount) {
if (amount > 0) {
balance += amount;
return true;
}
return false;
}
public boolean withdraw(double amount, String inputPassword) {
if (authenticate(inputPassword) && amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
public boolean changePassword(String oldPassword, String newPassword) {
if (authenticate(oldPassword)) {
setPassword(newPassword);
return true;
}
return false;
}
// Private helper method
private boolean authenticate(String inputPassword) {
return this.password.equals(inputPassword);
}
@Override
public String toString() {
return String.format("Account: %s, Holder: %s, Balance: $%.2f",
accountNumber, accountHolder, balance);
}
}
public class EncapsulationDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount("John Doe", "secure123");
// Can't access private fields directly
// account.balance = 1000; // COMPILE ERROR
// System.out.println(account.password); // COMPILE ERROR
// Must use public methods
account.deposit(1000.0);
System.out.println("Balance: $" + account.getBalance());
boolean success = account.withdraw(200.0, "secure123");
System.out.println("Withdrawal successful: " + success);
System.out.println("New balance: $" + account.getBalance());
// Attempt invalid withdrawal
success = account.withdraw(2000.0, "secure123");
System.out.println("Large withdrawal: " + success);
// Attempt with wrong password
success = account.withdraw(100.0, "wrongpass");
System.out.println("Wrong password withdrawal: " + success);
System.out.println("\n" + account);
}
}
2. Inheritance
Creating new classes from existing classes.
- Code reuse
- IS-A relationship
- Method overriding
- Polymorphism foundation
// Base class
class Vehicle {
private String make;
private String model;
private int year;
public Vehicle(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
public void start() {
System.out.println("Vehicle starting...");
}
public void stop() {
System.out.println("Vehicle stopping...");
}
public void displayInfo() {
System.out.printf("%s %s (%d)\n", make, model, year);
}
// Getters
public String getMake() { return make; }
public String getModel() { return model; }
public int getYear() { return year; }
}
// Derived class 1
class Car extends Vehicle {
private int doors;
private String fuelType;
public Car(String make, String model, int year, int doors, String fuelType) {
super(make, model, year); // Call parent constructor
this.doors = doors;
this.fuelType = fuelType;
}
// Method overriding
@Override
public void start() {
System.out.println("Car engine starting with key...");
}
// Additional method specific to Car
public void honk() {
System.out.println("Beep beep!");
}
@Override
public void displayInfo() {
super.displayInfo(); // Call parent method
System.out.printf("Doors: %d, Fuel: %s\n", doors, fuelType);
}
}
// Derived class 2
class Motorcycle extends Vehicle {
private boolean hasSidecar;
public Motorcycle(String make, String model, int year, boolean hasSidecar) {
super(make, model, year);
this.hasSidecar = hasSidecar;
}
@Override
public void start() {
System.out.println("Motorcycle starting with kick...");
}
// Additional method specific to Motorcycle
public void wheelie() {
System.out.println("Doing a wheelie!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Has sidecar: " + hasSidecar);
}
}
// Multi-level inheritance
class SportsCar extends Car {
private int topSpeed;
public SportsCar(String make, String model, int year, int topSpeed) {
super(make, model, year, 2, "Premium"); // Sports cars have 2 doors
this.topSpeed = topSpeed;
}
@Override
public void start() {
System.out.println("Sports car roaring to life!");
}
public void turboBoost() {
System.out.println("Turbo boost activated!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Top speed: " + topSpeed + " mph");
}
}
public class InheritanceDemo {
public static void main(String[] args) {
System.out.println("=== Inheritance Demonstration ===\n");
// Create objects of different types
Vehicle vehicle = new Vehicle("Generic", "Model", 2020);
Car car = new Car("Toyota", "Camry", 2023, 4, "Gasoline");
Motorcycle bike = new Motorcycle("Harley", "Davidson", 2022, false);
SportsCar sportsCar = new SportsCar("Ferrari", "488", 2024, 205);
// Demonstrate inheritance
System.out.println("1. Base class method:");
vehicle.start();
vehicle.displayInfo();
System.out.println("\n2. Car (inherits from Vehicle):");
car.start(); // Overridden method
car.honk(); // Car-specific method
car.displayInfo();
System.out.println("\n3. Motorcycle (inherits from Vehicle):");
bike.start(); // Overridden method
bike.wheelie(); // Motorcycle-specific method
bike.displayInfo();
System.out.println("\n4. SportsCar (inherits from Car which inherits from Vehicle):");
sportsCar.start(); // Overridden method
sportsCar.honk(); // Inherited from Car
sportsCar.turboBoost(); // SportsCar-specific method
sportsCar.displayInfo();
System.out.println("\n5. Polymorphism demonstration:");
Vehicle[] vehicles = new Vehicle[3];
vehicles[0] = car;
vehicles[1] = bike;
vehicles[2] = sportsCar;
for (Vehicle v : vehicles) {
v.start(); // Calls appropriate overridden method
}
System.out.println("\n6. instanceof operator:");
System.out.println("car instanceof Vehicle: " + (car instanceof Vehicle));
System.out.println("car instanceof Car: " + (car instanceof Car));
System.out.println("car instanceof SportsCar: " + (car instanceof SportsCar));
System.out.println("\n7. Type casting:");
if (sportsCar instanceof Car) {
Car c = (Car) sportsCar; // Upcasting (implicit)
c.honk();
// Downcasting (explicit, check with instanceof first)
if (c instanceof SportsCar) {
SportsCar sc = (SportsCar) c;
sc.turboBoost();
}
}
}
}
3. Polymorphism
One interface, multiple implementations.
- Method overriding (runtime)
- Method overloading (compile-time)
- Interface implementation
- Dynamic method dispatch
// Shape hierarchy demonstrating polymorphism
abstract class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
// Abstract methods (must be implemented by subclasses)
public abstract double getArea();
public abstract double getPerimeter();
// Concrete method
public void display() {
System.out.println("Color: " + color);
System.out.printf("Area: %.2f, Perimeter: %.2f\n", getArea(), getPerimeter());
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
// Additional method specific to Circle
public double getDiameter() {
return 2 * radius;
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
// Additional method specific to Rectangle
public boolean isSquare() {
return width == height;
}
}
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
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;
}
// Additional method specific to Triangle
public String getTriangleType() {
if (side1 == side2 && side2 == side3) return "Equilateral";
else if (side1 == side2 || side2 == side3 || side1 == side3) return "Isosceles";
else return "Scalene";
}
}
// Interface for drawing
interface Drawable {
void draw();
void setLineWidth(int width);
}
// Multiple inheritance through interfaces
class DrawableCircle extends Circle implements Drawable {
private int lineWidth;
public DrawableCircle(String color, double radius) {
super(color, radius);
this.lineWidth = 1;
}
@Override
public void draw() {
System.out.printf("Drawing circle with radius %.2f and line width %d\n",
getDiameter() / 2, lineWidth);
}
@Override
public void setLineWidth(int width) {
this.lineWidth = width;
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
System.out.println("=== Polymorphism Demonstration ===\n");
// Create shapes using polymorphism
Shape[] shapes = new Shape[4];
shapes[0] = new Circle("Red", 5.0);
shapes[1] = new Rectangle("Blue", 4.0, 6.0);
shapes[2] = new Triangle("Green", 3.0, 4.0, 5.0);
shapes[3] = new DrawableCircle("Yellow", 7.0);
// Process shapes polymorphically
System.out.println("Processing all shapes:");
for (Shape shape : shapes) {
System.out.println("\n--- " + shape.getClass().getSimpleName() + " ---");
shape.display(); // Calls appropriate overridden method
// Type checking and casting
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
System.out.println("Diameter: " + circle.getDiameter());
}
if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
System.out.println("Is square? " + rect.isSquare());
}
if (shape instanceof Triangle) {
Triangle tri = (Triangle) shape;
System.out.println("Triangle type: " + tri.getTriangleType());
}
if (shape instanceof Drawable) {
Drawable drawable = (Drawable) shape;
drawable.setLineWidth(2);
drawable.draw();
}
}
// Method overloading (compile-time polymorphism)
System.out.println("\n=== Method Overloading ===");
Calculator calc = new Calculator();
System.out.println("add(5, 3): " + calc.add(5, 3));
System.out.println("add(5.5, 3.2): " + calc.add(5.5, 3.2));
System.out.println("add(1, 2, 3): " + calc.add(1, 2, 3));
// Interface polymorphism
System.out.println("\n=== Interface Polymorphism ===");
Drawable drawable = new DrawableCircle("Purple", 3.0);
drawable.setLineWidth(3);
drawable.draw();
// Using shape as Drawable
if (shapes[3] instanceof Drawable) {
((Drawable) shapes[3]).draw();
}
}
}
// Helper Calculator class for overloading demonstration
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
4. Abstraction
Hiding implementation details, showing only functionality.
- Abstract classes
- Interfaces
- Hide complex reality
- Expose only essential features
// Database abstraction layer
// Abstract class
abstract class Database {
protected String connectionString;
protected boolean isConnected;
public Database(String connectionString) {
this.connectionString = connectionString;
this.isConnected = false;
}
// Abstract methods (implementation hidden)
public abstract boolean connect();
public abstract boolean disconnect();
public abstract ResultSet executeQuery(String query);
public abstract int executeUpdate(String query);
// Concrete methods
public boolean isConnected() {
return isConnected;
}
public String getConnectionString() {
return connectionString;
}
// Template method pattern
public final void performTransaction(String... queries) {
connect();
try {
for (String query : queries) {
executeUpdate(query);
}
System.out.println("Transaction completed successfully");
} finally {
disconnect();
}
}
}
// Interface for logging
interface Loggable {
void log(String message);
void setLogLevel(String level);
}
// Concrete implementation 1
class MySQLDatabase extends Database implements Loggable {
private String logLevel;
public MySQLDatabase(String host, String database, String user, String password) {
super(String.format("jdbc:mysql://%s/%s?user=%s&password=%s",
host, database, user, password));
this.logLevel = "INFO";
}
@Override
public boolean connect() {
System.out.println("Connecting to MySQL database...");
// Simulate connection logic
try {
Thread.sleep(1000); // Simulate connection delay
isConnected = true;
log("Connected to MySQL database");
return true;
} catch (InterruptedException e) {
log("Connection failed: " + e.getMessage());
return false;
}
}
@Override
public boolean disconnect() {
System.out.println("Disconnecting from MySQL...");
isConnected = false;
log("Disconnected from MySQL");
return true;
}
@Override
public ResultSet executeQuery(String query) {
log("Executing query: " + query);
// Simulate query execution
System.out.println("Executing MySQL query: " + query);
return new ResultSet(); // Simplified
}
@Override
public int executeUpdate(String query) {
log("Executing update: " + query);
// Simulate update execution
System.out.println("Executing MySQL update: " + query);
return 1; // Simplified - assume 1 row affected
}
// Loggable interface implementation
@Override
public void log(String message) {
System.out.println("[" + logLevel + "] MySQL: " + message);
}
@Override
public void setLogLevel(String level) {
this.logLevel = level;
}
// MySQL-specific method
public void enableSSL() {
System.out.println("SSL enabled for MySQL connection");
}
}
// Concrete implementation 2
class PostgreSQLDatabase extends Database {
public PostgreSQLDatabase(String host, String database, String user, String password) {
super(String.format("jdbc:postgresql://%s/%s?user=%s&password=%s",
host, database, user, password));
}
@Override
public boolean connect() {
System.out.println("Connecting to PostgreSQL database...");
// PostgreSQL-specific connection logic
isConnected = true;
return true;
}
@Override
public boolean disconnect() {
System.out.println("Disconnecting from PostgreSQL...");
isConnected = false;
return true;
}
@Override
public ResultSet executeQuery(String query) {
System.out.println("Executing PostgreSQL query: " + query);
return new ResultSet();
}
@Override
public int executeUpdate(String query) {
System.out.println("Executing PostgreSQL update: " + query);
return 1;
}
// PostgreSQL-specific method
public void enableJSONSupport() {
System.out.println("JSON support enabled");
}
}
// Simplified ResultSet class for demonstration
class ResultSet {
public boolean next() { return false; }
public String getString(int column) { return ""; }
}
public class AbstractionDemo {
public static void main(String[] args) {
System.out.println("=== Abstraction Demonstration ===\n");
// Using abstraction - we don't care about implementation details
Database db1 = new MySQLDatabase("localhost", "mydb", "admin", "password123");
Database db2 = new PostgreSQLDatabase("localhost", "mydb", "admin", "password123");
System.out.println("1. MySQL Database Operations:");
System.out.println("Connection string: " + db1.getConnectionString());
db1.connect();
db1.executeQuery("SELECT * FROM users");
db1.performTransaction(
"INSERT INTO users (name, email) VALUES ('John', 'john@email.com')",
"UPDATE users SET active = true WHERE name = 'John'"
);
System.out.println("\n2. PostgreSQL Database Operations:");
System.out.println("Connection string: " + db2.getConnectionString());
db2.connect();
db2.executeQuery("SELECT * FROM products");
// Accessing implementation-specific features
System.out.println("\n3. Implementation-Specific Features:");
if (db1 instanceof MySQLDatabase) {
MySQLDatabase mysql = (MySQLDatabase) db1;
mysql.setLogLevel("DEBUG");
mysql.enableSSL();
mysql.log("Testing debug logging");
}
if (db2 instanceof PostgreSQLDatabase) {
PostgreSQLDatabase pg = (PostgreSQLDatabase) db2;
pg.enableJSONSupport();
}
System.out.println("\n4. Benefits of Abstraction:");
System.out.println("- Hide database-specific implementation details");
System.out.println("- Provide consistent interface for all databases");
System.out.println("- Easy to switch database implementations");
System.out.println("- Focus on what needs to be done, not how");
System.out.println("\n5. Real-world analogy:");
System.out.println("Think of a car's steering wheel:");
System.out.println("- User knows HOW to turn it (abstraction)");
System.out.println("- User doesn't need to know the complex");
System.out.println(" mechanical linkage underneath (implementation)");
}
}
The Four Pillars of OOP - Summary
Encapsulation
Data hiding, controlled access through methods
Inheritance
Code reuse, IS-A relationship, hierarchy
Polymorphism
One interface, many forms, method overriding
Abstraction
Hide complexity, show essentials, interfaces
7. Best Practices & Common Mistakes
- God Objects: Classes that do too much (violates Single Responsibility)
- Anemic Domain Model: Classes with only getters/setters, no behavior
- Inheritance Misuse: Using inheritance for code reuse instead of IS-A relationship
- Public Fields: Exposing fields directly without encapsulation
- Circular Dependencies: Two classes depending on each other
- Over-engineering: Creating unnecessary abstraction layers
OOP Design Principles
- SRP: Single Responsibility Principle
- OCP: Open/Closed Principle
- LSP: Liskov Substitution Principle
- ISP: Interface Segregation Principle
- DIP: Dependency Inversion Principle
- DRY: Don't Repeat Yourself
- KISS: Keep It Simple, Stupid
- YAGNI: You Aren't Gonna Need It
Java OOP Best Practices
- Use meaningful class and method names
- Keep classes small and focused
- Prefer composition over inheritance
- Make fields private with getters/setters
- Use final for immutable fields
- Override equals(), hashCode(), and toString()
- Use interfaces for abstraction
- Document with Javadoc comments
// Demonstrating SOLID principles in Java OOP
public class OOPSolidPrinciples {
// ========== SINGLE RESPONSIBILITY PRINCIPLE ==========
// A class should have only one reason to change
// BAD: One class doing multiple things
class BadEmployee {
private String name;
private double salary;
public void calculateSalary() { /* ... */ }
public void saveToDatabase() { /* ... */ } // Different responsibility!
public void sendEmail() { /* ... */ } // Another responsibility!
public void generateReport() { /* ... */ } // Yet another!
}
// GOOD: Separate classes for separate responsibilities
class GoodEmployee {
private String name;
private double salary;
// Only employee-related logic
}
class EmployeeRepository {
public void save(GoodEmployee emp) { /* Database operations */ }
}
class EmailService {
public void sendEmail(GoodEmployee emp, String message) { /* Email logic */ }
}
class ReportGenerator {
public void generateReport(GoodEmployee emp) { /* Report logic */ }
}
// ========== OPEN/CLOSED PRINCIPLE ==========
// Open for extension, closed for modification
interface Discount {
double apply(double price);
}
class RegularDiscount implements Discount {
@Override
public double apply(double price) {
return price * 0.9; // 10% discount
}
}
class PremiumDiscount implements Discount {
@Override
public double apply(double price) {
return price * 0.8; // 20% discount
}
}
class HolidayDiscount implements Discount {
@Override
public double apply(double price) {
return price * 0.7; // 30% discount
}
}
// Adding new discount type doesn't require modifying existing code
// ========== LISKOV SUBSTITUTION PRINCIPLE ==========
// Subtypes must be substitutable for their base types
class Bird {
public void fly() {
System.out.println("Flying...");
}
}
// BAD: Penguin can't fly but inherits from Bird
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
// GOOD: Separate interfaces
interface FlyingBird {
void fly();
}
interface SwimmingBird {
void swim();
}
class Eagle implements FlyingBird {
@Override
public void fly() {
System.out.println("Eagle flying high");
}
}
class GoodPenguin implements SwimmingBird {
@Override
public void swim() {
System.out.println("Penguin swimming");
}
}
// ========== INTERFACE SEGREGATION PRINCIPLE ==========
// Clients shouldn't be forced to depend on interfaces they don't use
// BAD: One large interface
interface BadWorker {
void work();
void eat();
void sleep();
void code();
void test();
void deploy();
}
// GOOD: Segregated interfaces
interface Worker {
void work();
}
interface Eater {
void eat();
}
interface Sleeper {
void sleep();
}
interface Coder {
void code();
}
interface Tester {
void test();
}
interface Deployer {
void deploy();
}
// A class can implement only what it needs
class Developer implements Worker, Coder, Eater, Sleeper {
// Implement only required methods
}
// ========== DEPENDENCY INVERSION PRINCIPLE ==========
// Depend on abstractions, not concretions
// BAD: Direct dependency on concrete class
class BadLightBulb {
public void turnOn() { /* ... */ }
public void turnOff() { /* ... */ }
}
class BadSwitch {
private BadLightBulb bulb;
public BadSwitch() {
this.bulb = new BadLightBulb(); // Direct dependency
}
public void operate() {
// Can only work with LightBulb
}
}
// GOOD: Depend on abstraction
interface Switchable {
void turnOn();
void turnOff();
}
class GoodLightBulb implements Switchable {
@Override
public void turnOn() { System.out.println("Light bulb on"); }
@Override
public void turnOff() { System.out.println("Light bulb off"); }
}
class Fan implements Switchable {
@Override
public void turnOn() { System.out.println("Fan on"); }
@Override
public void turnOff() { System.out.println("Fan off"); }
}
class GoodSwitch {
private Switchable device;
public GoodSwitch(Switchable device) { // Dependency injection
this.device = device; // Can work with any Switchable
}
public void operate() {
device.turnOn();
// ... later
device.turnOff();
}
}
public static void main(String[] args) {
System.out.println("=== SOLID Principles in Java ===");
// Demonstrate Dependency Inversion
GoodLightBulb bulb = new GoodLightBulb();
Fan fan = new Fan();
GoodSwitch lightSwitch = new GoodSwitch(bulb);
GoodSwitch fanSwitch = new GoodSwitch(fan);
System.out.println("Operating light switch:");
lightSwitch.operate();
System.out.println("\nOperating fan switch:");
fanSwitch.operate();
System.out.println("\n=== Key Takeaways ===");
System.out.println("1. SRP: One class, one responsibility");
System.out.println("2. OCP: Extend, don't modify");
System.out.println("3. LSP: Substitutability is key");
System.out.println("4. ISP: Small, focused interfaces");
System.out.println("5. DIP: Depend on abstractions");
System.out.println("\n=== When to Use What ===");
System.out.println("Use inheritance for IS-A relationships");
System.out.println("Use composition for HAS-A relationships");
System.out.println("Use interfaces for CAN-DO relationships");
System.out.println("Use abstract classes for partial implementations");
}
}
Java OOP Mastery Checklist
Classes & Objects
- Can create classes with proper structure
- Understand object lifecycle
- Know when to use static vs instance
Constructors & Methods
- Can create multiple constructors
- Understand method overloading/overriding
- Know access modifiers usage
OOP Principles
- Implement encapsulation properly
- Use inheritance judiciously
- Apply polymorphism effectively
- Create appropriate abstractions
8. Practice Exercises
Exercise 1: Create a Library Management System
Design and implement a library system with the following requirements:
- Create a
Bookclass with title, author, ISBN, and availability status - Create a
Memberclass with name, member ID, and list of borrowed books - Create a
Libraryclass that manages books and members - Implement methods to:
- Add/remove books from library
- Register new members
- Borrow and return books
- Search books by title/author
- Display overdue books
- Use proper encapsulation with getters/setters
- Implement inheritance if appropriate (e.g., different book types)
- Use polymorphism for different member types (Student, Faculty)
Exercise 2: E-commerce Product System
Create an e-commerce product hierarchy:
- Create an abstract
Productclass with name, price, and description - Create concrete classes:
Electronics,Clothing,Book - Each product type should have specific attributes:
- Electronics: warranty period, power consumption
- Clothing: size, material, color
- Book: author, publisher, ISBN
- Create an interface
Discountablewith methodapplyDiscount() - Create a
ShoppingCartclass that can hold different products - Implement methods to:
- Add/remove products from cart
- Calculate total price
- Apply discounts to eligible products
- Display cart contents
Exercise 3: Bank Account Hierarchy
Implement a banking system with different account types:
- Create an abstract
BankAccountclass with account number, balance, and account holder - Create concrete classes:
SavingsAccount,CheckingAccount,LoanAccount - Each account type should have:
- SavingsAccount: interest rate, minimum balance
- CheckingAccount: overdraft limit, transaction fee
- LoanAccount: interest rate, loan term, collateral
- Create an interface
InterestBearingwith methodcalculateInterest() - Create a
Bankclass that manages all accounts - Implement methods to:
- Open/close accounts
- Deposit/withdraw funds
- Transfer between accounts
- Calculate and apply interest
- Generate account statements