- Introduction to OOP
- The Four Pillars of OOP
- Classes and Objects
- Constructors
- Access Modifiers
- Method Overloading and Overriding
- Abstract Classes
- Interfaces
- Composition vs Inheritance
- SOLID Principles
- Design Patterns
- Best Practices
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around data, or objects, rather than functions and logic. Java is a pure object-oriented language where everything revolves around classes and objects.
Key Concepts:
- Object: An instance of a class containing state (data) and behavior (methods)
- Class: A blueprint or template for creating objects
- Message Passing: Objects communicate by calling methods on each other
Encapsulation is the bundling of data and methods that operate on that data within a single unit (class), and restricting direct access to some components.
Example:
public class BankAccount {
// Private fields - data hiding
private String accountNumber;
private double balance;
private String ownerName;
// Constructor
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0;
}
// Public methods - controlled access
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
} else {
System.out.println("Invalid deposit amount");
}
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
return true;
} else {
System.out.println("Insufficient funds or invalid amount");
return false;
}
}
// Getter methods
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
}Benefits:
- Data security and integrity
- Flexibility to change implementation without affecting external code
- Better maintainability
Inheritance allows a class to inherit properties and methods from another class, promoting code reusability.
Example:
// Parent class (Superclass)
public class Vehicle {
protected String brand;
protected String model;
protected int year;
public Vehicle(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
}
public void start() {
System.out.println("Vehicle is starting...");
}
public void stop() {
System.out.println("Vehicle is stopping...");
}
public void displayInfo() {
System.out.println(year + " " + brand + " " + model);
}
}
// Child class (Subclass)
public class Car extends Vehicle {
private int numberOfDoors;
private String fuelType;
public Car(String brand, String model, int year, int numberOfDoors, String fuelType) {
super(brand, model, year); // Call parent constructor
this.numberOfDoors = numberOfDoors;
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();
System.out.println("Doors: " + numberOfDoors + ", Fuel: " + fuelType);
}
}
// Another child class
public class Motorcycle extends Vehicle {
private boolean hasSidecar;
public Motorcycle(String brand, String model, int year, boolean hasSidecar) {
super(brand, model, year);
this.hasSidecar = hasSidecar;
}
@Override
public void start() {
System.out.println("Motorcycle kick-starting...");
}
public void wheelie() {
System.out.println("Performing a wheelie!");
}
}Usage:
public class Main {
public static void main(String[] args) {
Car car = new Car("Toyota", "Camry", 2023, 4, "Hybrid");
car.displayInfo();
car.start();
car.honk();
Motorcycle bike = new Motorcycle("Harley-Davidson", "Sportster", 2022, false);
bike.displayInfo();
bike.start();
bike.wheelie();
}
}Polymorphism allows objects of different types to be treated as objects of a common parent type. It enables one interface to be used for a general class of actions.
Types:
- Compile-time (Static) Polymorphism: Method overloading
- Runtime (Dynamic) Polymorphism: Method overriding
Example:
// Parent class
public abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
// Abstract method - must be implemented by subclasses
public abstract void makeSound();
public void sleep() {
System.out.println(name + " is sleeping...");
}
}
// Subclasses
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " says: Woof! Woof!");
}
public void fetch() {
System.out.println(name + " is fetching the ball!");
}
}
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " says: Meow! Meow!");
}
public void scratch() {
System.out.println(name + " is scratching the furniture!");
}
}
public class Cow extends Animal {
public Cow(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " says: Moo! Moo!");
}
}
// Demonstrating polymorphism
public class AnimalShelter {
public static void main(String[] args) {
// Polymorphic array - parent type holding different child objects
Animal[] animals = new Animal[3];
animals[0] = new Dog("Buddy");
animals[1] = new Cat("Whiskers");
animals[2] = new Cow("Bessie");
// Same method call produces different results
for (Animal animal : animals) {
animal.makeSound(); // Polymorphic behavior
animal.sleep();
System.out.println();
}
// Method that accepts parent type
makeAnimalSound(new Dog("Max"));
makeAnimalSound(new Cat("Luna"));
}
public static void makeAnimalSound(Animal animal) {
animal.makeSound(); // Works with any Animal subclass
}
}Abstraction means hiding complex implementation details and showing only essential features of an object. It helps reduce programming complexity and effort.
Example:
// Abstract class
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// Abstract methods - no implementation
public abstract double calculateArea();
public abstract double calculatePerimeter();
// Concrete method
public void displayColor() {
System.out.println("Color: " + color);
}
}
// Concrete implementations
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
}
public 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 calculateArea() {
return width * height;
}
@Override
public double calculatePerimeter() {
return 2 * (width + height);
}
}
// Using abstraction
public class ShapeTest {
public static void main(String[] args) {
Shape circle = new Circle("Red", 5.0);
Shape rectangle = new Rectangle("Blue", 4.0, 6.0);
printShapeInfo(circle);
printShapeInfo(rectangle);
}
public static void printShapeInfo(Shape shape) {
shape.displayColor();
System.out.println("Area: " + shape.calculateArea());
System.out.println("Perimeter: " + shape.calculatePerimeter());
System.out.println();
}
}A class is a blueprint for creating objects, and an object is an instance of a class.
Example:
public class Student {
// Instance variables (attributes)
private String name;
private int rollNumber;
private double gpa;
// Static variable (class variable) - shared by all instances
private static int totalStudents = 0;
// Constructor
public Student(String name, int rollNumber, double gpa) {
this.name = name;
this.rollNumber = rollNumber;
this.gpa = gpa;
totalStudents++;
}
// Instance method
public void study(String subject) {
System.out.println(name + " is studying " + subject);
}
// Getter methods
public String getName() {
return name;
}
public double getGpa() {
return gpa;
}
// Setter method with validation
public void setGpa(double gpa) {
if (gpa >= 0.0 && gpa <= 4.0) {
this.gpa = gpa;
} else {
System.out.println("Invalid GPA");
}
}
// Static method - belongs to class, not instance
public static int getTotalStudents() {
return totalStudents;
}
// toString method for object representation
@Override
public String toString() {
return "Student{name='" + name + "', rollNumber=" + rollNumber + ", gpa=" + gpa + "}";
}
}
// Using the class
public class Main {
public static void main(String[] args) {
// Creating objects
Student student1 = new Student("Alice", 101, 3.8);
Student student2 = new Student("Bob", 102, 3.5);
Student student3 = new Student("Charlie", 103, 3.9);
// Accessing instance methods
student1.study("Mathematics");
student2.study("Physics");
// Accessing static method
System.out.println("Total students: " + Student.getTotalStudents());
// Using toString
System.out.println(student1);
}
}Constructors are special methods used to initialize objects. They have the same name as the class and no return type.
Example:
public class Employee {
private String name;
private String employeeId;
private double salary;
private String department;
// Default constructor
public Employee() {
this.name = "Unknown";
this.employeeId = "000";
this.salary = 0.0;
this.department = "Unassigned";
}
// Parameterized constructor
public Employee(String name, String employeeId) {
this.name = name;
this.employeeId = employeeId;
this.salary = 30000.0; // Default salary
this.department = "General";
}
// Constructor with all parameters
public Employee(String name, String employeeId, double salary, String department) {
this.name = name;
this.employeeId = employeeId;
this.salary = salary;
this.department = department;
}
// Copy constructor
public Employee(Employee other) {
this.name = other.name;
this.employeeId = other.employeeId;
this.salary = other.salary;
this.department = other.department;
}
public void displayInfo() {
System.out.println("Name: " + name + ", ID: " + employeeId +
", Salary: $" + salary + ", Dept: " + department);
}
}
// Usage
public class Main {
public static void main(String[] args) {
Employee emp1 = new Employee(); // Default constructor
Employee emp2 = new Employee("John Doe", "E001"); // Partial parameters
Employee emp3 = new Employee("Jane Smith", "E002", 75000, "Engineering");
Employee emp4 = new Employee(emp3); // Copy constructor
emp1.displayInfo();
emp2.displayInfo();
emp3.displayInfo();
emp4.displayInfo();
}
}Access modifiers control the visibility of classes, methods, and variables.
Types:
- private: Accessible only within the same class
- default (no modifier): Accessible within the same package
- protected: Accessible within the same package and subclasses
- public: Accessible from anywhere
Example:
// File: Person.java
package com.example.people;
public class Person {
private String ssn; // Only accessible within Person class
protected String name; // Accessible in subclasses and same package
String address; // Default - accessible in same package only
public int age; // Accessible everywhere
public Person(String ssn, String name, String address, int age) {
this.ssn = ssn;
this.name = name;
this.address = address;
this.age = age;
}
// Private method
private void calculateTaxes() {
System.out.println("Calculating taxes using SSN: " + ssn);
}
// Public method to access private method
public void processTaxReturn() {
calculateTaxes(); // Can call private method within same class
}
// Protected method
protected void updateName(String newName) {
this.name = newName;
}
}
// File: Employee.java
package com.example.people;
public class Employee extends Person {
private String employeeId;
public Employee(String ssn, String name, String address, int age, String employeeId) {
super(ssn, name, address, age);
this.employeeId = employeeId;
}
public void displayInfo() {
// System.out.println(ssn); // ERROR: Cannot access private member
System.out.println("Name: " + name); // OK: protected, accessed in subclass
System.out.println("Address: " + address); // OK: default, same package
System.out.println("Age: " + age); // OK: public
}
public void changeName(String newName) {
updateName(newName); // OK: protected method accessible in subclass
}
}Same method name with different parameters in the same class.
public class Calculator {
// Overloaded methods - same name, different parameters
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;
}
public String add(String a, String b) {
return a + b;
}
// Different number and type of parameters
public void display(String message) {
System.out.println("Message: " + message);
}
public void display(String message, int times) {
for (int i = 0; i < times; i++) {
System.out.println(message);
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls add(int, int)
System.out.println(calc.add(5.5, 10.3)); // Calls add(double, double)
System.out.println(calc.add(1, 2, 3)); // Calls add(int, int, int)
System.out.println(calc.add("Hello", "World")); // Calls add(String, String)
}
}Subclass provides specific implementation of a method already defined in parent class.
public class PaymentProcessor {
public void processPayment(double amount) {
System.out.println("Processing payment of $" + amount);
}
public double calculateFee(double amount) {
return amount * 0.02; // 2% fee
}
}
public class CreditCardProcessor extends PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount);
System.out.println("Verifying card details...");
System.out.println("Payment authorized!");
}
@Override
public double calculateFee(double amount) {
return amount * 0.03; // 3% fee for credit cards
}
}
public class PayPalProcessor extends PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount);
System.out.println("Redirecting to PayPal...");
System.out.println("Payment completed!");
}
@Override
public double calculateFee(double amount) {
return amount * 0.029 + 0.30; // 2.9% + $0.30 for PayPal
}
}
// Usage
public class Main {
public static void main(String[] args) {
PaymentProcessor processor1 = new CreditCardProcessor();
PaymentProcessor processor2 = new PayPalProcessor();
processor1.processPayment(100.0);
System.out.println("Fee: $" + processor1.calculateFee(100.0));
processor2.processPayment(100.0);
System.out.println("Fee: $" + processor2.calculateFee(100.0));
}
}Abstract classes cannot be instantiated and may contain abstract methods (without implementation) that must be implemented by subclasses.
Example:
public abstract class DatabaseConnection {
protected String connectionString;
protected boolean isConnected;
public DatabaseConnection(String connectionString) {
this.connectionString = connectionString;
this.isConnected = false;
}
// Abstract methods - must be implemented by subclasses
public abstract void connect();
public abstract void disconnect();
public abstract void executeQuery(String query);
// Concrete method - shared by all subclasses
public void logConnection() {
System.out.println("Connection to: " + connectionString);
System.out.println("Status: " + (isConnected ? "Connected" : "Disconnected"));
}
// Concrete method with implementation
public boolean isConnected() {
return isConnected;
}
}
public class MySQLConnection extends DatabaseConnection {
private int port;
public MySQLConnection(String connectionString, int port) {
super(connectionString);
this.port = port;
}
@Override
public void connect() {
System.out.println("Connecting to MySQL database on port " + port + "...");
isConnected = true;
System.out.println("MySQL connection established!");
}
@Override
public void disconnect() {
System.out.println("Closing MySQL connection...");
isConnected = false;
System.out.println("MySQL disconnected!");
}
@Override
public void executeQuery(String query) {
if (isConnected) {
System.out.println("Executing MySQL query: " + query);
} else {
System.out.println("Not connected to database!");
}
}
}
public class MongoDBConnection extends DatabaseConnection {
private String database;
public MongoDBConnection(String connectionString, String database) {
super(connectionString);
this.database = database;
}
@Override
public void connect() {
System.out.println("Connecting to MongoDB database: " + database + "...");
isConnected = true;
System.out.println("MongoDB connection established!");
}
@Override
public void disconnect() {
System.out.println("Closing MongoDB connection...");
isConnected = false;
System.out.println("MongoDB disconnected!");
}
@Override
public void executeQuery(String query) {
if (isConnected) {
System.out.println("Executing MongoDB query: " + query);
} else {
System.out.println("Not connected to database!");
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
DatabaseConnection mysql = new MySQLConnection("localhost", 3306);
DatabaseConnection mongo = new MongoDBConnection("localhost:27017", "mydb");
mysql.connect();
mysql.logConnection();
mysql.executeQuery("SELECT * FROM users");
mysql.disconnect();
System.out.println("\n---\n");
mongo.connect();
mongo.logConnection();
mongo.executeQuery("db.users.find()");
mongo.disconnect();
}
}Interfaces define a contract that classes must follow. They contain only abstract methods (until Java 8, which introduced default and static methods).
Example:
// Interface for drawable objects
public interface Drawable {
void draw(); // Abstract method (implicitly public abstract)
void resize(double scale);
}
// Interface for moveable objects
public interface Moveable {
void move(int x, int y);
int getX();
int getY();
}
// Class implementing single interface
public class Circle implements Drawable {
private double radius;
private String color;
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " circle with radius " + radius);
}
@Override
public void resize(double scale) {
radius *= scale;
System.out.println("Circle resized. New radius: " + radius);
}
}
// Class implementing multiple interfaces
public class Rectangle implements Drawable, Moveable {
private double width;
private double height;
private int x;
private int y;
private String color;
public Rectangle(double width, double height, int x, int y, String color) {
this.width = width;
this.height = height;
this.x = x;
this.y = y;
this.color = color;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " rectangle at (" + x + "," + y + ")");
System.out.println("Dimensions: " + width + " x " + height);
}
@Override
public void resize(double scale) {
width *= scale;
height *= scale;
System.out.println("Rectangle resized to: " + width + " x " + height);
}
@Override
public void move(int newX, int newY) {
this.x = newX;
this.y = newY;
System.out.println("Rectangle moved to: (" + x + "," + y + ")");
}
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
}
// Interface with default method (Java 8+)
public interface Printable {
void print();
// Default method with implementation
default void printWithBorder() {
System.out.println("====================");
print();
System.out.println("====================");
}
// Static method
static void printHeader(String title) {
System.out.println("=== " + title + " ===");
}
}
public class Document implements Printable {
private String content;
public Document(String content) {
this.content = content;
}
@Override
public void print() {
System.out.println(content);
}
}
// Usage
public class Main {
public static void main(String[] args) {
Circle circle = new Circle(5.0, "Red");
circle.draw();
circle.resize(1.5);
System.out.println("\n---\n");
Rectangle rect = new Rectangle(10.0, 5.0, 0, 0, "Blue");
rect.draw();
rect.move(10, 20);
rect.resize(2.0);
System.out.println("\n---\n");
Document doc = new Document("This is a sample document.");
doc.print();
doc.printWithBorder(); // Using default method
Printable.printHeader("My Document"); // Using static method
}
}Composition ("has-a" relationship) is often preferred over inheritance ("is-a" relationship) for better flexibility.
Example:
// Using Composition (Preferred approach)
// Component classes
public class Engine {
private String type;
private int horsepower;
public Engine(String type, int horsepower) {
this.type = type;
this.horsepower = horsepower;
}
public void start() {
System.out.println(type + " engine starting (" + horsepower + " HP)");
}
public void stop() {
System.out.println("Engine stopping");
}
}
public class Transmission {
private String type;
private int gears;
public Transmission(String type, int gears) {
this.type = type;
this.gears = gears;
}
public void shiftGear(int gear) {
System.out.println("Shifting to gear " + gear);
}
}
public class GPS {
public void navigate(String destination) {
System.out.println("Navigating to: " + destination);
}
}
// Main class using composition
public class Car {
private String model;
private Engine engine; // Car HAS-A Engine
private Transmission transmission; // Car HAS-A Transmission
private GPS gps; // Car HAS-A GPS (optional)
// Constructor injection
public Car(String model, Engine engine, Transmission transmission) {
this.model = model;
this.engine = engine;
this.transmission = transmission;
}
// Optional feature
public void installGPS(GPS gps) {
this.gps = gps;
}
public void start() {
System.out.println("Starting " + model);
engine.start();
}
public void drive(String destination) {
start();
transmission.shiftGear(1);
if (gps != null) {
gps.navigate(destination);
}
System.out.println("Driving to " + destination);
}
public void stop() {
engine.stop();
System.out.println(model + " stopped");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Engine v6Engine = new Engine("V6", 280);
Transmission automatic = new Transmission("Automatic", 8);
Car myCar = new Car("Toyota Camry", v6Engine, automatic);
myCar.installGPS(new GPS());
myCar.drive("Downtown");
myCar.stop();
System.out.println("\n---\n");
// Easy to swap components
Engine v8Engine = new Engine("V8", 450);
Car sportsCar = new Car("Mustang", v8Engine, automatic);
sportsCar.drive("Race Track");
sportsCar.stop();
}
}Advantages of Composition:
- More flexible: can change behavior at runtime
- Loose coupling between classes
- Easier to test and maintain
- Can combine behaviors from multiple sources
When to use Inheritance:
- Clear "is-a" relationship exists
- Need to use polymorphism
- Want to leverage existing code through extension
SOLID is an acronym for five design principles that make software more maintainable and flexible.
A class should have only one reason to change.
// BAD: Multiple responsibilities
public class Employee {
private String name;
private double salary;
public void calculateSalary() { /* ... */ }
public void saveToDatabase() { /* ... */ } // Database responsibility
public void generateReport() { /* ... */ } // Reporting responsibility
}
// GOOD: Separate responsibilities
public class Employee {
private String name;
private double salary;
public String getName() { return name; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
}
public class EmployeeRepository {
public void save(Employee employee) {
// Database logic here
System.out.println("Saving employee to database...");
}
public Employee findById(int id) {
// Database retrieval logic
return null;
}
}
public class SalaryCalculator {
public double calculateSalary(Employee employee) {
// Salary calculation logic
return employee.getSalary() * 1.1; // 10% bonus
}
}
public class EmployeeReportGenerator {
public void generateReport(Employee employee) {
// Report generation logic
System.out.println("Generating report for: " + employee.getName());
}
}Classes should be open for extension but closed for modification.
// BAD: Need to modify class to add new discount types
public class DiscountCalculator {
public double calculateDiscount(String customerType, double amount) {
if (customerType.equals("Regular")) {
return amount * 0.05;
} else if (customerType.equals("Premium")) {
return amount * 0.10;
} else if (customerType.equals("VIP")) {
return amount * 0.20;
}
return 0;
}
}
// GOOD: Use abstraction to extend without modifying
public interface DiscountStrategy {
double calculateDiscount(double amount);
}
public class RegularDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.05;
}
}
public class PremiumDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.10;
}
}
public class VIPDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.20;
}
}
public class ShoppingCart {
private DiscountStrategy discountStrategy;
public ShoppingCart(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double checkout(double amount) {
double discount = discountStrategy.calculateDiscount(amount);
return amount - discount;
}
}
// Adding new discount type doesn't require modifying existing code
public class SeasonalDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double amount) {
return amount * 0.15;
}
}Objects of a superclass should be replaceable with objects of subclasses without breaking the application.
// BAD: Violates LSP
public class Bird {
public void fly() {
System.out.println("Flying...");
}
}
public class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
// GOOD: Proper abstraction respecting LSP
public abstract class Bird {
public abstract void move();
public abstract void eat();
}
public class FlyingBird extends Bird {
@Override
public void move() {
fly();
}
public void fly() {
System.out.println("Flying through the air");
}
@Override
public void eat() {
System.out.println("Eating seeds");
}
}
public class Sparrow extends FlyingBird {
@Override
public void fly() {
System.out.println("Sparrow flying swiftly");
}
}
public class Penguin extends Bird {
@Override
public void move() {
swim();
}
public void swim() {
System.out.println("Swimming in water");
}
@Override
public void eat() {
System.out.println("Eating fish");
}
}
// Usage - can replace Bird with any subclass
public class BirdWatcher {
public void observeBird(Bird bird) {
bird.move(); // Works correctly for all bird types
bird.eat();
}
}Clients should not be forced to depend on interfaces they don't use.
// BAD: Fat interface forces unnecessary implementation
public interface Worker {
void work();
void eat();
void sleep();
void attendMeeting();
}
public class Robot implements Worker {
@Override
public void work() {
System.out.println("Robot working");
}
@Override
public void eat() {
// Robots don't eat - forced to implement
throw new UnsupportedOperationException();
}
@Override
public void sleep() {
// Robots don't sleep - forced to implement
throw new UnsupportedOperationException();
}
@Override
public void attendMeeting() {
throw new UnsupportedOperationException();
}
}
// GOOD: Segregated interfaces
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public interface MeetingAttendee {
void attendMeeting();
}
public class HumanWorker implements Workable, Eatable, Sleepable, MeetingAttendee {
@Override
public void work() {
System.out.println("Human working");
}
@Override
public void eat() {
System.out.println("Human eating lunch");
}
@Override
public void sleep() {
System.out.println("Human sleeping");
}
@Override
public void attendMeeting() {
System.out.println("Human attending meeting");
}
}
public class Robot implements Workable {
@Override
public void work() {
System.out.println("Robot working 24/7");
}
}High-level modules should not depend on low-level modules. Both should depend on abstractions.
// BAD: High-level class depends on low-level class
public class EmailService {
public void sendEmail(String message) {
System.out.println("Sending email: " + message);
}
}
public class Notification {
private EmailService emailService = new EmailService(); // Tight coupling
public void send(String message) {
emailService.sendEmail(message);
}
}
// GOOD: Both depend on abstraction
public interface MessageService {
void sendMessage(String message);
}
public class EmailService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending email: " + message);
}
}
public class SMSService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending SMS: " + message);
}
}
public class PushNotificationService implements MessageService {
@Override
public void sendMessage(String message) {
System.out.println("Sending push notification: " + message);
}
}
public class Notification {
private MessageService messageService;
// Dependency injection through constructor
public Notification(MessageService messageService) {
this.messageService = messageService;
}
public void send(String message) {
messageService.sendMessage(message);
}
}
// Usage - easy to switch implementations
public class Main {
public static void main(String[] args) {
Notification emailNotification = new Notification(new EmailService());
emailNotification.send("Hello via Email");
Notification smsNotification = new Notification(new SMSService());
smsNotification.send("Hello via SMS");
Notification pushNotification = new Notification(new PushNotificationService());
pushNotification.send("Hello via Push");
}
}Design patterns are reusable solutions to common software design problems.
Ensures a class has only one instance and provides global access to it.
public class Database {
// Private static instance
private static Database instance;
private String connectionString;
// Private constructor prevents instantiation
private Database() {
connectionString = "jdbc:mysql://localhost:3306/mydb";
System.out.println("Database connection initialized");
}
// Public method to get instance
public static Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}
// Thread-safe Singleton (better approach)
public class ThreadSafeDatabase {
private static volatile ThreadSafeDatabase instance;
private String connectionString;
private ThreadSafeDatabase() {
connectionString = "jdbc:mysql://localhost:3306/mydb";
}
public static ThreadSafeDatabase getInstance() {
if (instance == null) {
synchronized (ThreadSafeDatabase.class) {
if (instance == null) {
instance = new ThreadSafeDatabase();
}
}
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}
// Usage
public class Main {
public static void main(String[] args) {
Database db1 = Database.getInstance();
Database db2 = Database.getInstance();
System.out.println(db1 == db2); // true - same instance
db1.query("SELECT * FROM users");
}
}Creates objects without specifying the exact class to create.
// Product interface
public interface Vehicle {
void drive();
void stop();
}
// Concrete products
public class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Driving a car on the road");
}
@Override
public void stop() {
System.out.println("Car stopped");
}
}
public class Bike implements Vehicle {
@Override
public void drive() {
System.out.println("Riding a bike");
}
@Override
public void stop() {
System.out.println("Bike stopped");
}
}
public class Truck implements Vehicle {
@Override
public void drive() {
System.out.println("Driving a truck, hauling cargo");
}
@Override
public void stop() {
System.out.println("Truck stopped");
}
}
// Factory class
public class VehicleFactory {
public static Vehicle createVehicle(String type) {
switch (type.toLowerCase()) {
case "car":
return new Car();
case "bike":
return new Bike();
case "truck":
return new Truck();
default:
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
Vehicle car = VehicleFactory.createVehicle("car");
car.drive();
car.stop();
Vehicle bike = VehicleFactory.createVehicle("bike");
bike.drive();
bike.stop();
}
}Constructs complex objects step by step.
public class Computer {
// Required parameters
private String cpu;
private String ram;
// Optional parameters
private String storage;
private String gpu;
private boolean hasWifi;
private boolean hasBluetooth;
private Computer(ComputerBuilder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.gpu = builder.gpu;
this.hasWifi = builder.hasWifi;
this.hasBluetooth = builder.hasBluetooth;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", ram='" + ram + '\'' +
", storage='" + storage + '\'' +
", gpu='" + gpu + '\'' +
", hasWifi=" + hasWifi +
", hasBluetooth=" + hasBluetooth +
'}';
}
// Static nested Builder class
public static class ComputerBuilder {
// Required parameters
private String cpu;
private String ram;
// Optional parameters - initialized to default values
private String storage = "256GB SSD";
private String gpu = "Integrated";
private boolean hasWifi = false;
private boolean hasBluetooth = false;
public ComputerBuilder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public ComputerBuilder storage(String storage) {
this.storage = storage;
return this;
}
public ComputerBuilder gpu(String gpu) {
this.gpu = gpu;
return this;
}
public ComputerBuilder wifi(boolean hasWifi) {
this.hasWifi = hasWifi;
return this;
}
public ComputerBuilder bluetooth(boolean hasBluetooth) {
this.hasBluetooth = hasBluetooth;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Build computer with only required parameters
Computer basicComputer = new Computer.ComputerBuilder("Intel i5", "8GB")
.build();
System.out.println(basicComputer);
// Build computer with all features
Computer gamingComputer = new Computer.ComputerBuilder("Intel i9", "32GB")
.storage("1TB NVMe SSD")
.gpu("NVIDIA RTX 4090")
.wifi(true)
.bluetooth(true)
.build();
System.out.println(gamingComputer);
// Build computer with some optional features
Computer officeComputer = new Computer.ComputerBuilder("AMD Ryzen 5", "16GB")
.storage("512GB SSD")
.wifi(true)
.build();
System.out.println(officeComputer);
}
}Defines a one-to-many dependency where when one object changes state, all dependents are notified.
import java.util.ArrayList;
import java.util.List;
// Subject interface
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// Observer interface
public interface Observer {
void update(String news);
}
// Concrete Subject
public class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String latestNews;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(latestNews);
}
}
public void setNews(String news) {
this.latestNews = news;
System.out.println("\n[NEWS AGENCY] Breaking news published!");
notifyObservers();
}
}
// Concrete Observers
public class NewsChannel implements Observer {
private String channelName;
public NewsChannel(String channelName) {
this.channelName = channelName;
}
@Override
public void update(String news) {
System.out.println(channelName + " received news: " + news);
}
}
public class NewsWebsite implements Observer {
private String websiteName;
public NewsWebsite(String websiteName) {
this.websiteName = websiteName;
}
@Override
public void update(String news) {
System.out.println(websiteName + " published: " + news);
}
}
// Usage
public class Main {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
Observer cnn = new NewsChannel("CNN");
Observer bbc = new NewsChannel("BBC");
Observer website = new NewsWebsite("TechNews.com");
// Subscribe observers
agency.attach(cnn);
agency.attach(bbc);
agency.attach(website);
// Publish news - all observers notified
agency.setNews("Major earthquake hits California");
// Unsubscribe one observer
agency.detach(bbc);
// Publish another news
agency.setNews("New vaccine approved by FDA");
}
}Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
// Strategy interface
public interface PaymentStrategy {
void pay(double amount);
}
// Concrete strategies
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
private String cvv;
public CreditCardPayment(String cardNumber, String cvv) {
this.cardNumber = cardNumber;
this.cvv = cvv;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card ending in " +
cardNumber.substring(cardNumber.length() - 4));
}
}
public class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal account: " + email);
}
}
public class CryptoPayment implements PaymentStrategy {
private String walletAddress;
public CryptoPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Crypto wallet: " +
walletAddress.substring(0, 8) + "...");
}
}
// Context class
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
private double totalAmount;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void addItem(double price) {
totalAmount += price;
}
public void checkout() {
if (paymentStrategy == null) {
System.out.println("Please select a payment method");
return;
}
paymentStrategy.pay(totalAmount);
totalAmount = 0;
}
}
// Usage
public class Main {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Add items
cart.addItem(50.00);
cart.addItem(30.00);
cart.addItem(20.00);
// Pay with credit card
cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456", "123"));
cart.checkout();
// New shopping session
cart.addItem(75.00);
cart.addItem(25.00);
// Pay with PayPal
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout();
// Another session
cart.addItem(100.00);
// Pay with Crypto
cart.setPaymentStrategy(new CryptoPayment("1A2B3C4D5E6F7G8H9I0J"));
cart.checkout();
}
}Adds new functionality to objects dynamically without altering their structure.
// Component interface
public interface Coffee {
String getDescription();
double getCost();
}
// Concrete component
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 2.00;
}
}
// Abstract decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// Concrete decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.50;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public double getCost() {
return coffee.getCost() + 0.25;
}
}
public class VanillaDecorator extends CoffeeDecorator {
public VanillaDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Vanilla";
}
@Override
public double getCost() {
return coffee.getCost() + 0.75;
}
}
public class CaramelDecorator extends CoffeeDecorator {
public CaramelDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Caramel";
}
@Override
public double getCost() {
return coffee.getCost() + 1.00;
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Simple coffee
Coffee coffee1 = new SimpleCoffee();
System.out.println(coffee1.getDescription() + " - $" + coffee1.getCost());
// Coffee with milk
Coffee coffee2 = new MilkDecorator(new SimpleCoffee());
System.out.println(coffee2.getDescription() + " - $" + coffee2.getCost());
// Coffee with milk and sugar
Coffee coffee3 = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee3.getDescription() + " - $" + coffee3.getCost());
// Fancy coffee with all additions
Coffee coffee4 = new CaramelDecorator(
new VanillaDecorator(
new SugarDecorator(
new MilkDecorator(
new SimpleCoffee()))));
System.out.println(coffee4.getDescription() + " - $" + coffee4.getCost());
}
}Follow Java naming conventions for better code readability.
// Classes: PascalCase (noun)
public class CustomerAccount { }
public class PaymentProcessor { }
// Interfaces: PascalCase (adjective or noun)
public interface Drawable { }
public interface PaymentMethod { }
// Methods: camelCase (verb)
public void calculateTotalPrice() { }
public boolean isValid() { }
public String getUserName() { }
// Variables: camelCase (noun)
private String firstName;
private int accountBalance;
private boolean isActive;
// Constants: UPPER_SNAKE_CASE
public static final int MAX_RETRY_ATTEMPTS = 3;
public static final String DEFAULT_CURRENCY = "USD";
// Packages: lowercase
package com.company.project.module;public class BankAccount {
// Private fields
private double balance;
private String accountNumber;
// Constructor
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
// Getter with validation
public double getBalance() {
return balance;
}
// Setter with validation
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Withdrawal amount must be positive");
}
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
balance -= amount;
}
// Read-only property
public String getAccountNumber() {
return accountNumber;
}
}// Instead of deep inheritance hierarchies
public class Animal { }
public class Mammal extends Animal { }
public class Dog extends Mammal { }
// Use composition
public class Dog {
private AnimalBehavior behavior;
private DietType diet;
private Habitat habitat;
public Dog() {
this.behavior = new MammalBehavior();
this.diet = new CarnivoreDiet();
this.habitat = new LandHabitat();
}
}// BAD
public class Mgr {
private int d;
private List<String> lst;
public void proc() { }
}
// GOOD
public class CustomerManager {
private int daysUntilExpiration;
private List<String> customerEmailList;
public void processCustomerOrders() { }
}// BAD: Method doing too much
public void processOrder(Order order) {
// Validate order
// Calculate total
// Apply discounts
// Process payment
// Update inventory
// Send confirmation email
// Generate invoice
}
// GOOD: Break into smaller methods
public void processOrder(Order order) {
validateOrder(order);
double total = calculateTotal(order);
total = applyDiscounts(total, order.getCustomer());
processPayment(total, order.getPaymentMethod());
updateInventory(order.getItems());
sendConfirmationEmail(order.getCustomer());
generateInvoice(order);
}
private void validateOrder(Order order) {
if (order == null || order.getItems().isEmpty()) {
throw new IllegalArgumentException("Invalid order");
}
}
private double calculateTotal(Order order) {
return order.getItems().stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
}
// ... other focused methods// Program to interfaces, not implementations
public interface NotificationService {
void send(String message, String recipient);
}
public class EmailNotificationService implements NotificationService {
@Override
public void send(String message, String recipient) {
System.out.println("Sending email to " + recipient);
}
}
public class SMSNotificationService implements NotificationService {
@Override
public void send(String message, String recipient) {
System.out.println("Sending SMS to " + recipient);
}
}
// Client code depends on interface
public class OrderService {
private NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(Order order) {
// Process order...
notificationService.send("Order confirmed", order.getCustomerEmail());
}
}public class FileProcessor {
// Don't swallow exceptions
public void processFile(String filename) {
try {
// File processing logic
readFile(filename);
} catch (IOException e) {
// BAD: Empty catch block
// e.printStackTrace(); // Also bad in production
// GOOD: Log and/or rethrow
System.err.println("Failed to process file: " + filename);
throw new RuntimeException("File processing failed", e);
}
}
// Use specific exceptions
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Balance: " + balance + ", Requested: " + amount);
}
balance -= amount;
}
private double balance = 100.0;
private void readFile(String filename) throws IOException {
// File reading logic
}
}
// Custom exception
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}public class Example {
// Most restrictive access that makes sense
private String internalData; // Only within this class
protected void helperMethod() { } // This class and subclasses
void packageMethod() { } // Within same package
public void publicMethod() { } // Everywhere
}// BAD: God class doing everything
public class Application {
public void manageUsers() { }
public void processPayments() { }
public void sendEmails() { }
public void generateReports() { }
public void manageInventory() { }
// ... 50 more methods
}
// GOOD: Separate concerns
public class UserManager {
public void createUser(User user) { }
public void deleteUser(String userId) { }
}
public class PaymentProcessor {
public void processPayment(Payment payment) { }
public void refundPayment(String paymentId) { }
}
public class EmailService {
public void sendEmail(String to, String subject, String body) { }
}
public class ReportGenerator {
public Report generateSalesReport(Date startDate, Date endDate) { }
}public class TaxCalculator {
/**
* Calculates the total tax based on income and deductions.
*
* Tax brackets:
* - 0-10,000: 10%
* - 10,001-50,000: 20%
* - 50,001+: 30%
*
* @param income The gross income
* @param deductions Total deductions
* @return The calculated tax amount
*/
public double calculateTax(double income, double deductions) {
double taxableIncome = income - deductions;
double tax = 0;
if (taxableIncome <= 10000) {
tax = taxableIncome * 0.10;
} else if (taxableIncome <= 50000) {
tax = 1000 + (taxableIncome - 10000) * 0.20;
} else {
tax = 9000 + (taxableIncome - 50000) * 0.30;
}
return tax;
}
}// Instead of string constants
public class OrderStatus {
public static final String PENDING = "PENDING";
public static final String PROCESSING = "PROCESSING";
public static final String SHIPPED = "SHIPPED";
public static final String DELIVERED = "DELIVERED";
}
// Use enum
public enum OrderStatus {
PENDING("Order is pending"),
PROCESSING("Order is being processed"),
SHIPPED("Order has been shipped"),
DELIVERED("Order has been delivered"),
CANCELLED("Order was cancelled");
private String description;
OrderStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// Usage
public class Order {
private OrderStatus status;
public void updateStatus(OrderStatus newStatus) {
this.status = newStatus;
System.out.println("Status updated: " + newStatus.getDescription());
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
order.updateStatus(OrderStatus.PENDING);
order.updateStatus(OrderStatus.PROCESSING);
order.updateStatus(OrderStatus.SHIPPED);
}
}import java.util.Objects;
public class Person {
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age &&
Objects.equals(id, person.id) &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public String toString() {
return "Person{id='" + id + "', name='" + name + "', age=" + age + "}";
}
}import java.util.*;
public class CollectionExamples {
// Use interface types for declarations
public void goodPractice() {
List<String> names = new ArrayList<>(); // Good
Set<Integer> numbers = new HashSet<>(); // Good
Map<String, Integer> ages = new HashMap<>(); // Good
// Not: ArrayList<String> names = new ArrayList<>();
}
// Choose appropriate collection
public void chooseWisely() {
// Need fast indexed access? Use ArrayList
List<String> list = new ArrayList<>();
// Need unique elements? Use HashSet
Set<String> uniqueItems = new HashSet<>();
// Need sorted elements? Use TreeSet
Set<String> sortedItems = new TreeSet<>();
// Need key-value pairs? Use HashMap
Map<String, Integer> keyValue = new HashMap<>();
// Need thread-safe collection? Use concurrent collections
List<String> threadSafeList = Collections.synchronizedList(new ArrayList<>());
}
// Use generics properly
public <T> List<T> filterList(List<T> list, Predicate<T> condition) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (condition.test(item)) {
result.add(item);
}
}
return result;
}
}
interface Predicate<T> {
boolean test(T item);
}// Immutable class
public final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
// Create defensive copy of mutable object
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
// Return unmodifiable view
return Collections.unmodifiableList(hobbies);
}
}
// Usage
public class Main {
public static void main(String[] args) {
List<String> hobbies = Arrays.asList("Reading", "Gaming");
ImmutablePerson person = new ImmutablePerson("Alice", 25, hobbies);
// Cannot modify original
hobbies.set(0, "Cooking"); // Doesn't affect person object
// Cannot modify returned list
// person.getHobbies().add("Swimming"); // Throws exception
System.out.println(person.getName()); // Thread-safe, no worries!
}
}// BAD: Chain of method calls (violates Law of Demeter)
public class BadExample {
public void processOrder(Customer customer) {
String street = customer.getAddress().getStreet().getName();
// Too much knowledge about internal structure
}
}
// GOOD: Ask, don't navigate
public class GoodExample {
public void processOrder(Customer customer) {
String street = customer.getStreetName();
// Customer handles the internal navigation
}
}
public class Customer {
private Address address;
// Provide focused methods instead of exposing internal structure
public String getStreetName() {
return address != null ? address.getStreetName() : "Unknown";
}
}
public class Address {
private Street street;
public String getStreetName() {
return street != null ? street.getName() : "Unknown";
}
}
public class Street {
private String name;
public String getName() {
return name;
}
}Object-Oriented Programming in Java revolves around these key concepts:
Core Principles:
- Encapsulation: Bundle data and methods, control access
- Inheritance: Reuse code through parent-child relationships
- Polymorphism: Use one interface for different types
- Abstraction: Hide complexity, show only essentials
SOLID Principles:
- Single Responsibility: One class, one purpose
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Subclasses should be substitutable for parent classes
- Interface Segregation: Many specific interfaces better than one general
- Dependency Inversion: Depend on abstractions, not concrete classes
Best Practices:
- Use meaningful names following conventions
- Keep methods small and focused
- Favor composition over inheritance
- Program to interfaces, not implementations
- Handle exceptions properly
- Use appropriate access modifiers
- Document complex logic
- Write immutable classes when possible
- Follow design patterns for common problems
Design Patterns Covered:
- Singleton: Ensure single instance
- Factory: Create objects without specifying exact class
- Builder: Construct complex objects step by step
- Observer: Notify dependents of state changes
- Strategy: Encapsulate interchangeable algorithms
- Decorator: Add functionality dynamically
By mastering these OOP concepts and applying them consistently, you'll write cleaner, more maintainable, and more scalable Java applications. Remember that good design is iterative – refactor and improve your code as you learn and as requirements evolve.
- Official Java Documentation: https://docs.oracle.com/en/java/
- Effective Java by Joshua Bloch (Book)
- Head First Design Patterns by Freeman & Freeman (Book)
- Clean Code by Robert C. Martin (Book)
- Refactoring by Martin Fowler (Book)
Practice these concepts regularly, review your code, and always strive to write code that is easy to understand, maintain, and extend