- 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.,
ElectricCaris aCar).
1.3 Key Language Constraints
- Java suports single inheritance for classes (
class A extends Bonly), but allows multilevel inheritance (A → B → C). - Multiple inheritance of behavior is achieved via interfaces, not classes.
- 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 parentsuper.field— accesses shadowed fieldsuper(...)— 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, orfinalmethods. - Access level cannot be more restrictive (e.g., overriding a
protectedmethod withprivateis illegal). - Use
@Overrideannotation for compile-time safety.
- 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).
- 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);
}
}