Introduction
Imagine you're designing a blueprint for vehicles. You know all vehicles should have common features like start(), stop(), and honk(), but each specific vehicle (car, bike, truck) will implement these differently. Abstract classes in Java work exactly like thatβthey define a common structure that other classes must follow, while leaving the specific implementation details to the individual classes!
Abstract classes are like incomplete templates that establish what a class should do, but don't fully define how it should do it. They create a contract that subclasses must fulfill.
What are Abstract Classes?
Abstract classes are classes that cannot be instantiated and may contain abstract methods (methods without implementation). They serve as base classes for other classes to extend and provide partial or complete implementation.
Key Characteristics:
- β Cannot be instantiated: You cannot create objects of abstract classes
- β May contain abstract methods: Methods without implementation
- β Can have concrete methods: Regular methods with implementation
- β Can have constructors: Used for initializing common fields
- β Can have instance variables: Regular fields like any other class
- β Supports inheritance: Other classes extend abstract classes
Abstract Classes vs Interfaces
| Aspect | Abstract Class | Interface |
|---|---|---|
| Instantiation | Cannot be instantiated | Cannot be instantiated |
| Methods | Can have abstract and concrete methods | All methods abstract (before Java 8) |
| Variables | Can have any variables | Only public static final constants |
| Constructors | Can have constructors | No constructors |
| Inheritance | Single inheritance | Multiple implementation |
| Access Modifiers | Any access modifiers | Public (implicitly) |
Code Explanation with Examples
Example 1: Basic Abstract Class Structure
public class BasicAbstractClass {
public static void main(String[] args) {
System.out.println("=== BASIC ABSTRACT CLASS DEMO ===");
// β Cannot instantiate abstract class
// Animal animal = new Animal(); // Compilation error
// β
Can create concrete subclasses
Dog dog = new Dog("Buddy");
Cat cat = new Cat("Whiskers");
// Using abstract class references
Animal animal1 = new Dog("Rex");
Animal animal2 = new Cat("Mittens");
System.out.println("\n=== CALLING METHODS ===");
dog.makeSound();
dog.sleep();
cat.makeSound();
cat.sleep();
System.out.println("\n=== USING ABSTRACT REFERENCES ===");
animal1.makeSound();
animal2.makeSound();
System.out.println("\n=== POLYMORPHISM DEMO ===");
Animal[] animals = {dog, cat, animal1, animal2};
for (Animal animal : animals) {
animal.makeSound();
if (animal instanceof Dog) {
((Dog) animal).fetch(); // Specific to Dog
}
}
}
}
// Abstract class - cannot be instantiated
abstract class Animal {
// Instance variables - allowed in abstract classes
protected String name;
protected int age;
// Constructor - allowed in abstract classes
public Animal(String name) {
this.name = name;
this.age = 0;
System.out.println("πΎ Animal constructor called for: " + name);
}
// Abstract method - no implementation, must be implemented by subclasses
public abstract void makeSound();
// Concrete method - has implementation, can be used as-is or overridden
public void sleep() {
System.out.println(name + " is sleeping... π€");
}
// Another concrete method
public void eat() {
System.out.println(name + " is eating... π½οΈ");
}
// Regular getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// Concrete subclass - must implement all abstract methods
class Dog extends Animal {
private String breed;
public Dog(String name) {
super(name); // Call parent constructor
this.breed = "Unknown";
System.out.println("π Dog constructor called");
}
// Must implement abstract method
@Override
public void makeSound() {
System.out.println(name + " says: Woof! Woof! πΆ");
}
// Can override concrete methods
@Override
public void sleep() {
System.out.println(name + " is sleeping curled up... ππ€");
}
// Dog-specific method
public void fetch() {
System.out.println(name + " is fetching the ball! πΎ");
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
}
// Another concrete subclass
class Cat extends Animal {
private int lives = 9;
public Cat(String name) {
super(name); // Call parent constructor
System.out.println("π Cat constructor called");
}
// Must implement abstract method
@Override
public void makeSound() {
System.out.println(name + " says: Meow! Meow! π±");
}
// Override concrete method
@Override
public void eat() {
System.out.println(name + " is eating fish delicately... π");
}
// Cat-specific method
public void climb() {
System.out.println(name + " is climbing the tree! π³");
lives--; // Cats have 9 lives!
System.out.println(name + " has " + lives + " lives left");
}
public int getLives() {
return lives;
}
}
Output:
=== BASIC ABSTRACT CLASS DEMO === πΎ Animal constructor called for: Buddy π Dog constructor called πΎ Animal constructor called for: Whiskers π Cat constructor called πΎ Animal constructor called for: Rex π Dog constructor called πΎ Animal constructor called for: Mittens π Cat constructor called === CALLING METHODS === Buddy says: Woof! Woof! πΆ Buddy is sleeping curled up... ππ€ Whiskers says: Meow! Meow! π± Whiskers is sleeping... π€ === USING ABSTRACT REFERENCES === Rex says: Woof! Woof! πΆ Mittens says: Meow! Meow! π± === POLYMORPHISM DEMO === Buddy says: Woof! Woof! πΆ Buddy is fetching the ball! πΎ Whiskers says: Meow! Meow! π± Rex says: Woof! Woof! πΆ Rex is fetching the ball! πΎ Mittens says: Meow! Meow! π±
Example 2: Real-World Abstract Class Example - Payment System
public class PaymentSystemDemo {
public static void main(String[] args) {
System.out.println("=== PAYMENT SYSTEM DEMO ===");
// Create different payment methods
Payment creditCard = new CreditCardPayment("1234-5678-9012-3456", "John Doe", 100.0);
Payment paypal = new PayPalPayment("[email protected]", 50.0);
Payment crypto = new CryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 0.005, "BTC");
// Process payments
System.out.println("\n=== PROCESSING PAYMENTS ===");
processPayment(creditCard);
processPayment(paypal);
processPayment(crypto);
System.out.println("\n=== PAYMENT DETAILS ===");
displayPaymentDetails(creditCard);
displayPaymentDetails(paypal);
displayPaymentDetails(crypto);
// Test refund
System.out.println("\n=== REFUND TEST ===");
boolean refundSuccess = creditCard.refund(25.0);
System.out.println("Refund successful: " + refundSuccess);
}
public static void processPayment(Payment payment) {
System.out.println("\n--- Processing Payment ---");
boolean success = payment.processPayment();
if (success) {
System.out.println("β
Payment processed successfully!");
System.out.println("Transaction ID: " + payment.getTransactionId());
} else {
System.out.println("β Payment failed!");
}
}
public static void displayPaymentDetails(Payment payment) {
System.out.println("\n--- Payment Details ---");
payment.displayPaymentDetails();
System.out.println("Amount: $" + payment.getAmount());
System.out.println("Status: " + payment.getStatus());
}
}
// Abstract class defining the payment contract
abstract class Payment {
protected String transactionId;
protected double amount;
protected String status;
protected java.time.LocalDateTime timestamp;
// Constructor
public Payment(double amount) {
this.amount = amount;
this.status = "PENDING";
this.timestamp = java.time.LocalDateTime.now();
this.transactionId = generateTransactionId();
}
// Abstract methods - must be implemented by subclasses
public abstract boolean processPayment();
public abstract boolean refund(double refundAmount);
public abstract void displayPaymentDetails();
// Concrete methods - common implementation for all payments
public String getTransactionId() {
return transactionId;
}
public double getAmount() {
return amount;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public java.time.LocalDateTime getTimestamp() {
return timestamp;
}
// Common utility method
protected String generateTransactionId() {
return "TXN" + System.currentTimeMillis() + "_" + (int)(Math.random() * 1000);
}
// Common validation method
protected boolean validateAmount(double amount) {
return amount > 0;
}
// Template method pattern - defines skeleton of payment process
public final boolean processPaymentTemplate() {
System.out.println("π Starting payment process...");
if (!validateAmount(amount)) {
System.out.println("β Invalid amount: " + amount);
return false;
}
if (!preProcess()) {
System.out.println("β Pre-processing failed");
return false;
}
boolean result = processPayment(); // Calls abstract method
if (result) {
postProcessSuccess();
} else {
postProcessFailure();
}
return result;
}
// Hook methods - can be overridden by subclasses
protected boolean preProcess() {
System.out.println("β
Pre-processing completed");
return true;
}
protected void postProcessSuccess() {
System.out.println("β
Post-processing for successful payment");
this.status = "COMPLETED";
}
protected void postProcessFailure() {
System.out.println("β οΈ Post-processing for failed payment");
this.status = "FAILED";
}
}
// Concrete implementation for Credit Card payments
class CreditCardPayment extends Payment {
private String cardNumber;
private String cardHolderName;
private String cvv = "123"; // In real system, this would be encrypted
public CreditCardPayment(String cardNumber, String cardHolderName, double amount) {
super(amount);
this.cardNumber = maskCardNumber(cardNumber);
this.cardHolderName = cardHolderName;
}
@Override
public boolean processPayment() {
System.out.println("π³ Processing credit card payment...");
// Simulate payment processing
boolean success = Math.random() > 0.2; // 80% success rate
if (success) {
System.out.println("β
Credit card payment approved");
setStatus("APPROVED");
return true;
} else {
System.out.println("β Credit card payment declined");
setStatus("DECLINED");
return false;
}
}
@Override
public boolean refund(double refundAmount) {
if (refundAmount <= 0 || refundAmount > amount) {
System.out.println("β Invalid refund amount");
return false;
}
System.out.println("π³ Processing credit card refund...");
System.out.println("Refunding $" + refundAmount + " to card: " + cardNumber);
setStatus("REFUNDED");
return true;
}
@Override
public void displayPaymentDetails() {
System.out.println("Payment Method: Credit Card");
System.out.println("Card: " + cardNumber);
System.out.println("Card Holder: " + cardHolderName);
System.out.println("Transaction Time: " + timestamp);
}
private String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 4) {
return "****";
}
return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
}
// Override hook method for additional validation
@Override
protected boolean preProcess() {
System.out.println("π Validating credit card details...");
if (cardNumber == null || cardNumber.length() != 19) { // masked length
System.out.println("β Invalid card number");
return false;
}
return true;
}
}
// Concrete implementation for PayPal payments
class PayPalPayment extends Payment {
private String email;
private String paypalTransactionId;
public PayPalPayment(String email, double amount) {
super(amount);
this.email = email;
}
@Override
public boolean processPayment() {
System.out.println("π§ Processing PayPal payment...");
System.out.println("Sending payment request to: " + email);
// Simulate PayPal processing
boolean success = Math.random() > 0.1; // 90% success rate
if (success) {
this.paypalTransactionId = "PP" + System.currentTimeMillis();
System.out.println("β
PayPal payment completed");
System.out.println("PayPal Transaction ID: " + paypalTransactionId);
setStatus("COMPLETED");
return true;
} else {
System.out.println("β PayPal payment failed");
setStatus("FAILED");
return false;
}
}
@Override
public boolean refund(double refundAmount) {
System.out.println("π§ Processing PayPal refund...");
System.out.println("Refunding $" + refundAmount + " to: " + email);
if (paypalTransactionId != null) {
System.out.println("Original Transaction: " + paypalTransactionId);
}
setStatus("REFUNDED");
return true;
}
@Override
public void displayPaymentDetails() {
System.out.println("Payment Method: PayPal");
System.out.println("Email: " + email);
if (paypalTransactionId != null) {
System.out.println("PayPal Transaction ID: " + paypalTransactionId);
}
System.out.println("Transaction Time: " + timestamp);
}
}
// Concrete implementation for Cryptocurrency payments
class CryptoPayment extends Payment {
private String walletAddress;
private double cryptoAmount;
private String cryptocurrency;
public CryptoPayment(String walletAddress, double cryptoAmount, String cryptocurrency) {
super(cryptoAmount * 50000); // Convert to USD (simplified)
this.walletAddress = walletAddress;
this.cryptoAmount = cryptoAmount;
this.cryptocurrency = cryptocurrency;
}
@Override
public boolean processPayment() {
System.out.println("βΏ Processing cryptocurrency payment...");
System.out.println("Sending " + cryptoAmount + " " + cryptocurrency + " to: " + maskWalletAddress(walletAddress));
// Simulate blockchain confirmation
System.out.println("β³ Waiting for blockchain confirmation...");
try {
Thread.sleep(1000); // Simulate network delay
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
boolean success = Math.random() > 0.3; // 70% success rate
if (success) {
System.out.println("β
Cryptocurrency payment confirmed on blockchain");
setStatus("CONFIRMED");
return true;
} else {
System.out.println("β Cryptocurrency payment failed - transaction not confirmed");
setStatus("FAILED");
return false;
}
}
@Override
public boolean refund(double refundAmount) {
System.out.println("βΏ Cryptocurrency refunds are not typically supported");
System.out.println("β οΈ Once confirmed, crypto transactions are irreversible");
return false;
}
@Override
public void displayPaymentDetails() {
System.out.println("Payment Method: Cryptocurrency");
System.out.println("Cryptocurrency: " + cryptocurrency);
System.out.println("Amount: " + cryptoAmount + " " + cryptocurrency + " ($" + amount + ")");
System.out.println("Wallet: " + maskWalletAddress(walletAddress));
System.out.println("Transaction Time: " + timestamp);
}
private String maskWalletAddress(String address) {
if (address == null || address.length() < 8) {
return address;
}
return address.substring(0, 6) + "..." + address.substring(address.length() - 4);
}
// Override to disable amount validation for crypto (can be very small amounts)
@Override
protected boolean validateAmount(double amount) {
return amount >= 0; // Allow zero for dust amounts
}
}
Output:
=== PAYMENT SYSTEM DEMO === === PROCESSING PAYMENTS === --- Processing Payment --- π³ Processing credit card payment... β Credit card payment approved β Payment processed successfully! Transaction ID: TXN1705300000000_123 --- Processing Payment --- π§ Processing PayPal payment... Sending payment request to: [email protected] β PayPal payment completed PayPal Transaction ID: PP1705300001000 β Payment processed successfully! Transaction ID: TXN1705300001000_456 --- Processing Payment --- βΏ Processing cryptocurrency payment... Sending 0.005 BTC to: 1A1zP1...fNa β³ Waiting for blockchain confirmation... β Cryptocurrency payment confirmed on blockchain β Payment processed successfully! Transaction ID: TXN1705300002000_789 === PAYMENT DETAILS === --- Payment Details --- Payment Method: Credit Card Card: ****-****-****-3456 Card Holder: John Doe Transaction Time: 2024-01-15T10:30:00.123 Amount: $100.0 Status: APPROVED --- Payment Details --- Payment Method: PayPal Email: [email protected] PayPal Transaction ID: PP1705300001000 Transaction Time: 2024-01-15T10:30:00.124 Amount: $50.0 Status: COMPLETED --- Payment Details --- Payment Method: Cryptocurrency Cryptocurrency: BTC Amount: 0.005 BTC ($250.0) Wallet: 1A1zP1...fNa Transaction Time: 2024-01-15T10:30:00.125 Amount: $250.0 Status: CONFIRMED === REFUND TEST === π³ Processing credit card refund... Refunding $25.0 to card: ****-****-****-3456 Refund successful: true
Example 3: Abstract Class with Mixed Abstract and Concrete Methods
public class MixedMethodsDemo {
public static void main(String[] args) {
System.out.println("=== MIXED METHODS DEMO ===");
// Create different shapes
Shape circle = new Circle(5.0);
Shape rectangle = new Rectangle(4.0, 6.0);
Shape triangle = new Triangle(3.0, 4.0, 5.0);
// Demonstrate polymorphism
Shape[] shapes = {circle, rectangle, triangle};
for (Shape shape : shapes) {
System.out.println("\n--- " + shape.getClass().getSimpleName() + " ---");
shape.displayInfo();
System.out.printf("Area: %.2f%n", shape.calculateArea());
System.out.printf("Perimeter: %.2f%n", shape.calculatePerimeter());
if (shape instanceof Resizable) {
((Resizable) shape).resize(1.5); // 150% size
System.out.println("After resizing:");
System.out.printf("Area: %.2f%n", shape.calculateArea());
}
}
// Test abstract class with no abstract methods
System.out.println("\n=== CONCRETE SUBCLASS ===");
ConcreteShape concrete = new ConcreteShape();
concrete.displayInfo();
}
}
// Abstract class with both abstract and concrete methods
abstract class Shape {
protected String name;
protected String color;
public Shape() {
this.name = "Unknown Shape";
this.color = "Black";
}
public Shape(String name, String color) {
this.name = name;
this.color = color;
}
// Abstract methods - must be implemented by subclasses
public abstract double calculateArea();
public abstract double calculatePerimeter();
// Concrete methods - provided implementation
public void displayInfo() {
System.out.println("Shape: " + name);
System.out.println("Color: " + color);
}
public void setColor(String color) {
this.color = color;
System.out.println(name + " color changed to: " + color);
}
// Final method - cannot be overridden
public final String getDescription() {
return "This is a " + color + " " + name;
}
// Static method - belongs to class, not instances
public static void printShapeFact() {
System.out.println("π Shapes are defined by their boundaries and properties");
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getColor() { return color; }
}
// Interface for resizable shapes
interface Resizable {
void resize(double scale);
}
// Concrete subclass - Circle
class Circle extends Shape implements Resizable {
private double radius;
public Circle(double radius) {
super("Circle", "Red");
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
@Override
public void displayInfo() {
super.displayInfo(); // Call parent implementation
System.out.println("Radius: " + radius);
System.out.println("Diameter: " + (2 * radius));
}
@Override
public void resize(double scale) {
this.radius *= scale;
System.out.println("Circle resized by factor: " + scale);
System.out.println("New radius: " + radius);
}
public double getRadius() { return radius; }
public void setRadius(double radius) { this.radius = radius; }
}
// Concrete subclass - Rectangle
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
super("Rectangle", "Blue");
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
@Override
public double calculatePerimeter() {
return 2 * (width + height);
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Width: " + width + ", Height: " + height);
}
public boolean isSquare() {
return width == height;
}
public double getWidth() { return width; }
public void setWidth(double width) { this.width = width; }
public double getHeight() { return height; }
public void setHeight(double height) { this.height = height; }
}
// Concrete subclass - Triangle
class Triangle extends Shape implements Resizable {
private double sideA, sideB, sideC;
public Triangle(double sideA, double sideB, double sideC) {
super("Triangle", "Green");
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
@Override
public double calculateArea() {
// Using Heron's formula
double s = calculatePerimeter() / 2;
return Math.sqrt(s * (s - sideA) * (s - sideB) * (s - sideC));
}
@Override
public double calculatePerimeter() {
return sideA + sideB + sideC;
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Sides: " + sideA + ", " + sideB + ", " + sideC);
System.out.println("Type: " + getTriangleType());
}
@Override
public void resize(double scale) {
this.sideA *= scale;
this.sideB *= scale;
this.sideC *= scale;
System.out.println("Triangle resized by factor: " + scale);
}
private String getTriangleType() {
if (sideA == sideB && sideB == sideC) return "Equilateral";
if (sideA == sideB || sideA == sideC || sideB == sideC) return "Isosceles";
return "Scalene";
}
}
// Concrete class extending abstract class with no abstract methods
class ConcreteShape extends Shape {
// This class doesn't need to implement abstract methods
// because another concrete class might have already implemented them
// but typically you'd want to implement them or make this class abstract
public ConcreteShape() {
super("Concrete Shape", "Yellow");
}
@Override
public double calculateArea() {
return 42.0; // Arbitrary implementation
}
@Override
public double calculatePerimeter() {
return 24.0; // Arbitrary implementation
}
}
Output:
=== MIXED METHODS DEMO === --- Circle --- Shape: Circle Color: Red Radius: 5.0 Diameter: 10.0 Area: 78.54 Perimeter: 31.42 Circle resized by factor: 1.5 New radius: 7.5 After resizing: Area: 176.71 --- Rectangle --- Shape: Rectangle Color: Blue Width: 4.0, Height: 6.0 Area: 24.00 Perimeter: 20.00 --- Triangle --- Shape: Triangle Color: Green Sides: 3.0, 4.0, 5.0 Type: Scalene Area: 6.00 Perimeter: 12.00 Triangle resized by factor: 1.5 After resizing: Area: 13.50 === CONCRETE SUBCLASS === Shape: Concrete Shape Color: Yellow Area: 42.00 Perimeter: 24.00
Example 4: Common Pitfalls and Best Practices
public class AbstractClassPitfalls {
public static void main(String[] args) {
System.out.println("=== ABSTRACT CLASS PITFALLS AND BEST PRACTICES ===");
// Pitfall 1: Trying to instantiate abstract class
System.out.println("1. INSTANTIATION PITFALL:");
// β This would cause compilation error:
// BaseEmployee employee = new BaseEmployee();
// Best Practice 1: Use concrete subclasses
System.out.println("\n2. PROPER USAGE:");
FullTimeEmployee fullTime = new FullTimeEmployee("Alice", 5000);
PartTimeEmployee partTime = new PartTimeEmployee("Bob", 25, 80);
fullTime.displayInfo();
partTime.displayInfo();
// Pitfall 2: Forgetting to implement abstract methods
System.out.println("\n3. IMPLEMENTATION REQUIREMENT:");
// β This class wouldn't compile:
// class IncompleteEmployee extends BaseEmployee { }
// Must implement all abstract methods
// Best Practice 2: Template Method Pattern
System.out.println("\n4. TEMPLATE METHOD PATTERN:");
Report pdfReport = new PDFReport();
Report htmlReport = new HTMLReport();
pdfReport.generateReport();
System.out.println();
htmlReport.generateReport();
}
}
// β PITFALL 1: Abstract class with no abstract methods
abstract class BaseEmployee {
protected String name;
protected double salary;
public BaseEmployee(String name) {
this.name = name;
}
// No abstract methods - but class is still abstract
// This is legal but might be confusing
public void displayInfo() {
System.out.println("Employee: " + name);
System.out.println("Salary: $" + salary);
}
// Concrete method
public double getAnnualSalary() {
return salary * 12;
}
}
// β
BEST PRACTICE: Concrete subclass must be instantiable
class FullTimeEmployee extends BaseEmployee {
public FullTimeEmployee(String name, double monthlySalary) {
super(name);
this.salary = monthlySalary;
}
// No abstract methods to implement since parent has none
}
class PartTimeEmployee extends BaseEmployee {
private double hourlyRate;
private int hoursPerMonth;
public PartTimeEmployee(String name, double hourlyRate, int hoursPerMonth) {
super(name);
this.hourlyRate = hourlyRate;
this.hoursPerMonth = hoursPerMonth;
this.salary = hourlyRate * hoursPerMonth;
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Hourly Rate: $" + hourlyRate);
System.out.println("Hours/Month: " + hoursPerMonth);
}
}
// β
BEST PRACTICE: Template Method Pattern
abstract class Report {
protected String title;
protected String content;
// Template method - defines skeleton
public final void generateReport() {
initializeReport();
generateHeader();
generateBody();
generateFooter();
finalizeReport();
}
// Concrete methods
private void initializeReport() {
System.out.println("π Initializing report generation...");
}
private void finalizeReport() {
System.out.println("β
Report generation completed!");
}
// Abstract methods - must be implemented by subclasses
protected abstract void generateHeader();
protected abstract void generateBody();
protected abstract void generateFooter();
// Hook method - can be overridden
protected void addWatermark() {
System.out.println("π§ Adding default watermark...");
}
}
class PDFReport extends Report {
public PDFReport() {
this.title = "PDF Report";
this.content = "PDF specific content";
}
@Override
protected void generateHeader() {
System.out.println("π Generating PDF header with company logo...");
}
@Override
protected void generateBody() {
System.out.println("π Generating PDF body with tables and charts...");
}
@Override
protected void generateFooter() {
System.out.println("π Generating PDF footer with page numbers...");
}
@Override
protected void addWatermark() {
System.out.println("π§ Adding 'CONFIDENTIAL' watermark to PDF...");
}
}
class HTMLReport extends Report {
public HTMLReport() {
this.title = "HTML Report";
this.content = "HTML specific content";
}
@Override
protected void generateHeader() {
System.out.println("π Generating HTML header with navigation...");
}
@Override
protected void generateBody() {
System.out.println("π Generating HTML body with responsive design...");
}
@Override
protected void generateFooter() {
System.out.println("π Generating HTML footer with links...");
}
}
// β PITFALL: Abstract class that should probably be an interface
abstract class AnimalBehavior {
// This might be better as an interface
public abstract void makeSound();
public abstract void move();
// But we have concrete methods too...
public void breathe() {
System.out.println("Breathing...");
}
}
// β
BEST PRACTICE: When to use abstract class vs interface
interface Flyable {
void fly();
double getMaxAltitude();
}
interface Swimmable {
void swim();
double getMaxDepth();
}
// Abstract class can implement multiple interfaces
abstract class Bird implements Flyable {
protected String species;
public Bird(String species) {
this.species = species;
}
// Can provide partial implementation
@Override
public abstract void fly();
@Override
public double getMaxAltitude() {
return 1000.0; // Default implementation
}
public void chirp() {
System.out.println(species + " is chirping!");
}
}
class Duck extends Bird implements Swimmable {
public Duck() {
super("Duck");
}
@Override
public void fly() {
System.out.println("Duck is flying at low altitude");
}
@Override
public void swim() {
System.out.println("Duck is swimming gracefully");
}
@Override
public double getMaxDepth() {
return 3.0; // Ducks can dive 3 meters
}
@Override
public double getMaxAltitude() {
return 500.0; // Ducks fly lower than most birds
}
}
Output:
=== ABSTRACT CLASS PITFALLS AND BEST PRACTICES === 1. INSTANTIATION PITFALL: 2. PROPER USAGE: Employee: Alice Salary: $5000.0 Employee: Bob Salary: $2000.0 Hourly Rate: $25.0 Hours/Month: 80 3. IMPLEMENTATION REQUIREMENT: 4. TEMPLATE METHOD PATTERN: π Initializing report generation... π Generating PDF header with company logo... π Generating PDF body with tables and charts... π Generating PDF footer with page numbers... π§ Adding 'CONFIDENTIAL' watermark to PDF... β Report generation completed! π Initializing report generation... π Generating HTML header with navigation... π Generating HTML body with responsive design... π Generating HTML footer with links... π§ Adding default watermark... β Report generation completed!
When to Use Abstract Classes
β Appropriate Uses:
- Shared base implementation: When multiple classes share common code
- Template method pattern: Defining algorithm skeleton with varying steps
- Partial implementation: Providing some implementation but requiring subclasses to complete
- Common state: When subclasses need to share instance variables
- Controlled inheritance: When you want to control how classes are extended
β Avoid When:
- Multiple inheritance needed: Use interfaces instead
- No shared code: If classes don't share implementation
- Lightweight contract: When you only need method signatures
- Future flexibility: Interfaces are more flexible for evolution
Best Practices
- Use abstract classes for "is-a" relationships, interfaces for "can-do" relationships
- Provide meaningful default implementations for common methods
- Use template method pattern for algorithms with varying steps
- Make abstract classes package-private if they're implementation details
- Document abstract methods thoroughly with JavaDoc
- Consider making classes final if they shouldn't be subclassed
- Use constructor parameters to enforce required initialization
Common Patterns
// Pattern 1: Template Method Pattern
public abstract class DataProcessor {
// Template method
public final void process() {
loadData();
transformData();
saveData();
cleanup();
}
protected abstract void loadData();
protected abstract void transformData();
protected abstract void saveData();
protected void cleanup() {
// Default implementation, can be overridden
}
}
// Pattern 2: Base class with common functionality
public abstract class BaseEntity {
protected final String id;
protected final long createdAt;
protected BaseEntity(String id) {
this.id = id;
this.createdAt = System.currentTimeMillis();
}
public abstract void validate();
}
// Pattern 3: Partial implementation
public abstract class ListAdapter implements List {
// Implement some methods, leave others abstract
public int size() { return 0; }
public boolean isEmpty() { return size() == 0; }
// ... other methods remain abstract
}
Conclusion
Abstract classes are Java's way of creating blueprints for classes:
- β Define contracts: Specify what subclasses must implement
- β Share code: Provide common implementation for subclasses
- β Control inheritance: Enforce specific design patterns
- β Support polymorphism: Enable flexible, extensible designs
Key Takeaways:
- Abstract classes cannot be instantiated directly
- Can have both abstract and concrete methods
- Subclasses must implement all abstract methods (unless they're also abstract)
- Great for template method pattern and code reuse
- Use when you have shared state and behavior among related classes
Abstract classes strike the perfect balance between defining requirements and providing implementation, making them essential for building robust, maintainable object-oriented systems in Java!