Java Inheritance, Abstract Classes, Template Pattern, and Initialization Blocks

  1. Class Inheritance in Java

1.1 Implementing Inheritance

Inheritance enables code reuse and establishes an "is-a" relationship between classes. A subclass inherits accessible members (fields and methods) from its superclass using the extends keyword.

class Vehicle {
    protected String brand = "Generic";
    public void start() {
        System.out.println("Engine started.");
    }
}

class Car extends Vehicle {
    private int doors = 4;
    public void honk() {
        System.out.println("Beep beep!");
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.start(); // inherited
        myCar.honk();  // own method
        System.out.println("Brand: " + myCar.brand);
    }
}

1.2 Benefits and Trade-offs

  • Advantages: Promotes DRY (Don’t Repeat Yourself), simplifies maintenance, and supports polymorphism.
  • Drawbacks: Increases coupling—changes to the superclass may force updates in subclasses. Use only when a true hierarchical relationship exists (e.g., ElectricCar is a Car).

1.3 Key Language Constraints

  • Java suports single inheritance for classes (class A extends B only), but allows multilevel inheritance (A → B → C).
  • Multiple inheritance of behavior is achieved via interfaces, not classes.
  1. Member Resolution and Superclass Interaction

2.1 Varible Access Order

Wheen resolving a variable name, Java follows the **local → instance → superclass** scope chain:

class Parent {
    String label = "Parent";
}
class Child extends Parent {
    String label = "Child";
    void display() {
        String label = "Local";
        System.out.println(label);        // "Local"
        System.out.println(this.label);   // "Child"
        System.out.println(super.label);  // "Parent"
    }
}

2.2 The super Keyword

super provides explicit access to superclass members:

  • super.method() — invokes overridden method in parent
  • super.field — accesses shadowed field
  • super(...) — calls superclass constructor (must be first statement)

2.3 Constructor Chaining

All subclass constructors implicitly or explicitly invoke a superclass constructor. If no explicit super(...) is present, the compiler inserts super(). If the superclass lacks a no-arg constructor, you must provide one—or call an available parameterized constructor:

class Base {
    Base(String msg) { System.out.println(msg); }
}
class Derived extends Base {
    Derived() {
        super("Initializing base"); // required
    }
}

2.4 Method Overriding Rules

To override a method, the subclass method must match the signature and accessibility of the parent’s method:

  • Cannot override private, static, or final methods.
  • Access level cannot be more restrictive (e.g., overriding a protected method with private is illegal).
  • Use @Override annotation for compile-time safety.
  1. Abstract Classes and the Template Method Pattern

3.1 Purpose and Syntax

An abstract class defines a common interface and partial implementation. It may contain both concrete methods and abstract methods—those without bodies. Any class with at least one abstract method must be declared abstract.

abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // Concrete method
    public String getColor() {
        return color;
    }

    // Abstract method — must be implemented by subclasses
    public abstract double calculateArea();
}

3.2 Instantiation and Subclass Requirements

  • Abstract classes cannot be instantiated directly.
  • Non-abstract subclasses must implement all inherited abstract methods—or themselves be declared abstract.
  • Constructors in abstract classes are used during subclass instantiation (via super(...)).

3.3 Template Method Pattern

This behavioral pattern defines a skeleton algorithm in a method (often final) that delegates variant steps to abstract or hook methods. Subclasses customize behavior without altering the overall flow.

abstract class ReportGenerator {
    // Template method — fixed structure
    public final void generateReport() {
        System.out.println("=== REPORT START ===");
        renderHeader();
        renderBody();
        renderFooter();
        System.out.println("=== REPORT END ===");
    }

    protected abstract void renderHeader();
    protected abstract void renderBody();
    protected void renderFooter() {
        System.out.println("Generated on: " + java.time.LocalDate.now());
    }
}

class SalesReport extends ReportGenerator {
    @Override
    protected void renderHeader() {
        System.out.println("📊 Monthly Sales Summary");
    }

    @Override
    protected void renderBody() {
        System.out.println("- Total Revenue: $125,000");
        System.out.println("- New Customers: 84");
    }
}

3.4 Using final Strategically

The final modifier enforces immutability:

  • final class: prevents inheritance.
  • final method: prevents overriding (ideal for template methods).
  • final variable: ensures reference stability (for objects) or value constancy (for primitives).
  1. Initialization Blocks

4.1 Types and Execution Order

Java supports three kinds of initialization blocks:

  • Static blocks: Run once, at class loading time. Used for static resource setup (e.g., initializing caches, reading config files).
  • Instance blocks: Run before every constructor, after implicit super() but before constructor body. Ideal for shared initialization logic across multiple constructors.
  • Local blocks: Enclosed in braces inside methods; limit scope and aid memory management.

4.2 Practical Example

class DatabaseConnection {
    private static String driver;
    private final String url;

    // Static block — runs once per class load
    static {
        System.out.println("Loading database driver...");
        driver = "org.postgresql.Driver";
    }

    // Instance block — runs before each constructor
    {
        System.out.println("Setting default URL...");
        this.url = "jdbc:postgresql://localhost:5432/app";
    }

    DatabaseConnection() {}

    DatabaseConnection(String customUrl) {
        this.url = customUrl;
    }

    public String getUrl() { return url; }
}

4.3 Real-World Integration

Static blocks simplify bootstrapping in data-access layers—for example, pre-populating an in-memory student registry:

class StudentRepository {
    private static final Student[] students = new Student[100];
    
    static {
        students[0] = new Student("S001", "Alex Chen", 20, "2003-05-12");
        students[1] = new Student("S002", "Taylor Reed", 21, "2002-11-30");
        // ... initialize more
    }

    public static Student[] getAll() {
        return Arrays.stream(students)
                     .filter(Objects::nonNull)
                     .toArray(Student[]::new);
    }
}

Tags: java Inheritance abstract-class template-method initialization-block

Posted on Thu, 14 May 2026 06:26:39 +0000 by phpwiz