Lab 07: Inheritance & Polymorphism
Objective
Use extends to build class hierarchies, override methods, call super, understand polymorphism, and apply the Liskov Substitution Principle.
Background
Inheritance lets you build specialized classes from general ones, reusing and extending behavior. Polymorphism — "many forms" — means a reference of type Animal can hold a Dog or Cat, and the correct method runs at runtime. These mechanisms enable frameworks, plugin systems, and extensible designs.
Time
40 minutes
Prerequisites
Lab 06 (OOP — Classes)
Tools
Java 21 (Eclipse Temurin)
Docker image:
innozverse-java:latest
Lab Instructions
Step 1: extends and method overriding
💡
@Overrideannotation is crucial — it asks the compiler to verify you're actually overriding a parent method. Without it, a typo likepubilc double Area()silently creates a new method instead of overriding, and the bug is invisible until runtime.
📸 Verified Output:
Step 2: super — Calling Parent Methods
💡
super.method()calls the parent's implementation from within an override. This is the "extend, don't replace" pattern.ElectricVehicle.status()adds battery info to the vehicle's status string without duplicating the base formatting logic.
📸 Verified Output:
Step 3: Polymorphism in Practice
💡
processAlldoesn't know what type of payment it's processing — it just callsp.process()andp.getMethod(). Adding a new payment type (Apple Pay, Stripe) requires zero changes toprocessAll. This is Open/Closed Principle: open for extension, closed for modification.
📸 Verified Output:
Step 4: Abstract Classes vs Concrete Classes
💡 The Template Method Pattern uses
finalon the skeleton method to prevent overriding the algorithm structure, whileabstractmethods let subclasses customize each step.finalon a method means "I define the algorithm; you fill in the blanks."
📸 Verified Output:
Step 5: final, sealed Classes
💡 Sealed interfaces (Java 17+) list all permitted implementations. Combined with pattern matching switch, the compiler verifies your switch is exhaustive — you can't forget to handle a case. This is how Rust's enums and Haskell's ADTs work; Java finally caught up.
📸 Verified Output:
Step 6: Method Hiding vs Overriding
💡 Static methods are not polymorphic. They resolve based on the compile-time type of the reference, not the runtime type of the object. This is why "method hiding" (for statics) is a separate concept from "method overriding" (for instance methods). Avoid calling static methods via instance references — it's confusing.
📸 Verified Output:
Step 7: Covariant Return Types & Fluent Inheritance
💡 Covariant return types (Java 5+) let overriding methods return a more specific type.
Cat.create()returnsCatinstead ofAnimal— callers who know they have aCatget back aCatwithout casting. Fluent builders use the self-type pattern<T extends Builder<T>>to preserve the subtype through method chains.
📸 Verified Output:
Step 8: Full Example — Employee Hierarchy
💡
Manager.calculatePay()callssuper.calculatePay()to reuse the monthly calculation logic and add the bonus on top. This is the key benefit of inheritance: you add or modify behavior without copying code. WhenbaseSalarychanges inFullTime,Managerautomatically benefits.
📸 Verified Output:
Verification
Summary
You've covered extends, @Override, super, abstract classes, polymorphism, sealed interfaces, static vs instance dispatch, covariant returns, and the employee payroll hierarchy. Inheritance is powerful — use it when you have a genuine "is-a" relationship and need to share behavior, not just to reuse code.
Further Reading
Last updated
