3.3 Inheritance, Method Overriding, and Polymorphism
Key Takeaways
- Method overriding requires matching method signatures, equal or broader access privilege, covariant return types, and no new or broader checked exceptions.
- Virtual method invocation resolves instance method calls dynamically at runtime based on the actual object type in heap memory.
- Static methods cannot be overridden; re-declaring a static method with the same signature in a subclass hides the parent static method based on reference type.
- Fields and static members are bound at compile time based on the reference type and do not participate in polymorphic dispatch.
Inheritance, Method Overriding, and Polymorphism
Inheritance and polymorphism are the dual cornerstones of object-oriented design in Java SE 21. Inheritance enables subclasses to inherit state and behavior from an ancestor class using extends, while polymorphism enables uniform interaction with diverse object implementations through Dynamic Method Dispatch (Virtual Method Invocation).
Single Inheritance and Member Propagation
Java enforces single class inheritance: a class can directly extend at most one superclass. If no extends clause is declared, the class implicitly extends java.lang.Object.
What Is Inherited vs. What Is Not
- Inherited Members: Subclasses inherit all
publicandprotectedfields and methods. Subclasses in the same package also inherit package-private fields and methods. - Non-Inherited Members:
- Constructors: Constructors are never inherited; subclasses must declare their own constructors and chain to superclass constructors via
super(...). - Private Members: Private fields and methods are not inherited (they exist in the object state in memory, but are inaccessible by name in the subclass body).
- Static Members: Static members are accessible via class inheritance, but they do not participate in polymorphism; static methods are hidden, not overridden.
- Constructors: Constructors are never inherited; subclasses must declare their own constructors and chain to superclass constructors via
The Six Strict Rules of Method Overriding
When a subclass declares an instance method intended to override a superclass instance method, it must satisfy all six rules of the Java Language Specification (JLS §8.4.8):
class StorageException extends Exception {}
class DiskFullException extends StorageException {}
class DataRepository {
protected CharSequence loadRecord(String key) throws StorageException {
return "Generic Data: " + key;
}
}
class SqlDataRepository extends DataRepository {
// 1. Identical Signature: loadRecord(String)
// 2. Broader Access: public >= protected
// 3. Covariant Return: String is a subtype of CharSequence
// 4. Narrower Checked Exception: DiskFullException is a subtype of StorageException
@Override
public String loadRecord(String key) throws DiskFullException {
return "SQL Data: " + key;
}
}
Breakdown of the Six Overriding Rules
- Exact Signature Match: The method name and the ordered parameter type list must be identical. Altering parameter types (e.g.,
loadRecord(CharSequence key)) creates an overloaded method rather than an overriding method. - Equal or Broader Access Privilege: The overriding method cannot reduce visibility.
publicin superclass $ ightarrow$ must bepublicin subclass.protectedin superclass $ ightarrow$ can beprotectedorpublic.- Package-private in superclass $
ightarrow$ can be package-private,
protected, orpublic. privatemethods cannot be overridden; defining a method with the same signature in a subclass creates a completely new, unrelated method.
- Covariant Return Types: The return type of the overriding method must be identical to, or a subtype of, the return type declared in the superclass method.
- Reference Types: Subtypes are permitted (e.g.,
StringoverridesCharSequence,ArrayListoverridesList). - Primitive Types: Primitive return types must match identically. You cannot substitute
longforintor autoboxIntegerforint.
- Reference Types: Subtypes are permitted (e.g.,
- Checked Exception Broadening Restrictions: The overriding method cannot declare new or broader checked exceptions. It may declare:
- Exactly the same checked exceptions,
- Narrower subclasses of the declared checked exceptions,
- Fewer checked exceptions (or omit throws entirely),
- Any unchecked exceptions (
RuntimeException,Error, or their subclasses).
- Instance vs. Static Consistency: An instance method cannot override a
staticmethod (compile error), and astaticmethod cannot hide an instance method (compile error). - Non-Final Superclass Method: A method marked
finalin a superclass cannot be overridden. Attempting to override a final method generates a compile-time error.
Dynamic Method Dispatch (Virtual Method Invocation) vs. Method Hiding
Understanding how the JVM resolves instance method calls versus static method calls is one of the most critical topics on the 1Z0-830 exam.
class Vehicle {
public static void announce() {
System.out.println("Static Vehicle Announcement");
}
public void startEngine() {
System.out.println("Vehicle engine roaring");
}
}
class SportsCar extends Vehicle {
// HIDES Vehicle.announce()
public static void announce() {
System.out.println("Static SportsCar Announcement");
}
// OVERRIDES Vehicle.startEngine()
@Override
public void startEngine() {
System.out.println("SportsCar V8 engine roaring");
}
}
Execution Behavior and Resolution
public class PolymorphismDemo {
public static void main(String[] args) {
Vehicle v = new SportsCar();
// STATIC METHOD HIDING: Resolved at COMPILE TIME based on Reference Type (Vehicle)
v.announce(); // Prints: "Static Vehicle Announcement"
// INSTANCE METHOD VMI: Resolved at RUNTIME based on Actual Object Type (SportsCar)
v.startEngine(); // Prints: "SportsCar V8 engine roaring"
}
}
| Dimension | Method Overriding (Instance Methods) | Method Hiding (Static Methods) |
|---|---|---|
| Invocation Mechanism | Virtual Method Invocation (invokevirtual) | Static Method Binding (invokestatic) |
| Resolution Timing | Runtime (evaluates heap object type) | Compile-time (evaluates declared reference type) |
| Polymorphism | Fully polymorphic | Non-polymorphic |
@Override Annotation | Required / Recommended | Compilation error if applied |
Field Shadowing and Compile-Time Variable Binding
In Java, fields and variables are NEVER polymorphic. When a subclass declares a field with the same name as a field in its superclass, the subclass field shadows (hides) the superclass field.
class BaseSensor {
public int reading = 10;
public int getReading() { return reading; }
}
class PrecisionSensor extends BaseSensor {
public int reading = 99; // Shadows BaseSensor.reading
@Override
public int getReading() { return reading; }
}
public class FieldShadowDemo {
public static void main(String[] args) {
BaseSensor sensor = new PrecisionSensor();
// DIRECT FIELD ACCESS: Bound at compile-time by Reference Type (BaseSensor)
System.out.println("Field: " + sensor.reading); // Prints: 10
// POLYMORPHIC METHOD: Bound at runtime by Object Type (PrecisionSensor)
System.out.println("Method: " + sensor.getReading()); // Prints: 99
}
}
Polymorphic Casting Mechanics and ClassCastException
Java supports two categories of reference type casting:
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal { public void bark() { System.out.println("Woof"); } }
class Cat extends Mammal {}
public class CastingDemo {
public static void main(String[] args) {
// 1. UPCASTING: Widening reference conversion (Implicit & always safe)
Animal a = new Dog();
Mammal m = (Mammal) a; // Explicit upcast is valid but redundant
// 2. DOWNCASTING: Narrowing reference conversion (Requires explicit cast)
Dog d = (Dog) a; // Valid downcast: 'a' points to Dog instance on heap
d.bark();
// 3. INVALID DOWNCAST: Compiles but throws ClassCastException at runtime
Animal catAnimal = new Cat();
// Dog invalidDog = (Dog) catAnimal; // Throws ClassCastException: Cat cannot be cast to Dog
// 4. SAFE CASTING: Pattern Matching for instanceof (Java 16+ / 21)
if (catAnimal instanceof Dog activeDog) {
activeDog.bark(); // Only entered if catAnimal is truly a Dog
} else {
System.out.println("catAnimal is not a Dog");
}
}
}
Compiler vs. Runtime Casting Rules
- Unrelated Class Types: The compiler inspects the inheritance hierarchy. If two classes have no ancestor/descendant relationship and cannot possibly reference the same instance, the compiler rejects the cast with an
inconvertible typeserror (e.g.,String s = (String) new Dog();). - Interface Casting: An explicit cast from a class to an interface will always compile (unless the class is declared
finaland does not implement the interface), because a subclass could potentially implement the interface at runtime. - Runtime Verification: At runtime, the JVM inspects the actual object type on the heap. If the object does not pass the
instanceofcheck for the target type, aClassCastExceptionis immediately thrown.
Common 1Z0-830 Exam Traps
- Primitive Return Type Mismatch in Overrides: Changing a return type from
inttolongis NOT a covariant return; it is a compile-time error. Covariant returns only apply to subtype reference types. - Broader Checked Exceptions in Overrides: If the parent throws
IOException, the child cannot throwExceptionorThrowable. It CAN throwFileNotFoundExceptionor no checked exceptions. - Calling Hidden Static Methods on References:
Parent p = new Child(); p.staticMethod();invokesParent.staticMethod(), notChild.staticMethod(). - Field Access on Polymorphic Variables:
p.fieldevaluates the field inParent, even ifppoints tonew Child()andChilddefinesfield.
Examine the following code:
What is printed to the console?class Top {
public int num = 10;
public void print() { System.out.print("Top:" + num + " "); }
}
class Bottom extends Top {
public int num = 20;
public void print() { System.out.print("Bottom:" + num + " "); }
}
public class Test {
public static void main(String[] args) {
Top t = new Bottom();
System.out.print(t.num + " ");
t.print();
}
}
Given the superclass method declaration:
Which of the following method declarations is a legal override in a subclass?public class DocumentStore {
protected List<String> retrieve(String key) throws IOException {
return List.of();
}
}
Consider the following class definitions:
What is the output when Dispatcher is executed?class NetworkService {
public static void ping() {
System.out.print("Net-Ping ");
}
}
class CloudService extends NetworkService {
public static void ping() {
System.out.print("Cloud-Ping ");
}
}
public class Dispatcher {
public static void main(String[] args) {
NetworkService ns = new CloudService();
CloudService cs = new CloudService();
ns.ping();
cs.ping();
}
}
Given the following class hierarchy and execution block:
What occurs when running Inspector?class Building {}
class House extends Building {}
class Skyscraper extends Building {}
public class Inspector {
public static void inspect(Building b) {
House h = (House) b; // Line X
System.out.print("House ");
}
public static void main(String[] args) {
inspect(new Skyscraper());
}
}