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.
Last updated: September 2026

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 public and protected fields 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.

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

  1. 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.
  2. Equal or Broader Access Privilege: The overriding method cannot reduce visibility.
    • public in superclass $ ightarrow$ must be public in subclass.
    • protected in superclass $ ightarrow$ can be protected or public.
    • Package-private in superclass $ ightarrow$ can be package-private, protected, or public.
    • private methods cannot be overridden; defining a method with the same signature in a subclass creates a completely new, unrelated method.
  3. 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., String overrides CharSequence, ArrayList overrides List).
    • Primitive Types: Primitive return types must match identically. You cannot substitute long for int or autobox Integer for int.
  4. 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).
  5. Instance vs. Static Consistency: An instance method cannot override a static method (compile error), and a static method cannot hide an instance method (compile error).
  6. Non-Final Superclass Method: A method marked final in 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"
    }
}
DimensionMethod Overriding (Instance Methods)Method Hiding (Static Methods)
Invocation MechanismVirtual Method Invocation (invokevirtual)Static Method Binding (invokestatic)
Resolution TimingRuntime (evaluates heap object type)Compile-time (evaluates declared reference type)
PolymorphismFully polymorphicNon-polymorphic
@Override AnnotationRequired / RecommendedCompilation 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

  1. 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 types error (e.g., String s = (String) new Dog();).
  2. Interface Casting: An explicit cast from a class to an interface will always compile (unless the class is declared final and does not implement the interface), because a subclass could potentially implement the interface at runtime.
  3. Runtime Verification: At runtime, the JVM inspects the actual object type on the heap. If the object does not pass the instanceof check for the target type, a ClassCastException is immediately thrown.

Common 1Z0-830 Exam Traps

  • Primitive Return Type Mismatch in Overrides: Changing a return type from int to long is 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 throw Exception or Throwable. It CAN throw FileNotFoundException or no checked exceptions.
  • Calling Hidden Static Methods on References: Parent p = new Child(); p.staticMethod(); invokes Parent.staticMethod(), not Child.staticMethod().
  • Field Access on Polymorphic Variables: p.field evaluates the field in Parent, even if p points to new Child() and Child defines field.
Loading diagram...
Virtual Method Invocation vs Field Shadowing
Test Your Knowledge

Examine the following code:

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();
    }
}
What is printed to the console?

A
B
C
D
Test Your Knowledge

Given the superclass method declaration:

public class DocumentStore {
    protected List<String> retrieve(String key) throws IOException {
        return List.of();
    }
}
Which of the following method declarations is a legal override in a subclass?

A
B
C
D
Test Your Knowledge

Consider the following class definitions:

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();
    }
}
What is the output when Dispatcher is executed?

A
B
C
D
Test Your Knowledge

Given the following class hierarchy and execution block:

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());
    }
}
What occurs when running Inspector?

A
B
C
D