UNC COMP 301 - F26/Lec 5 - Polymorphism, Type casting, Method Access, Overriding methods
Watch on YouTube →
Overview
Muhammad Sayeed Ghani explains Java polymorphism by separating a reference’s compile-time type from its object’s runtime type, then applies that distinction to upcasting, downcasting, type-hierarchy graphs, and method access. He connects inheritance to overriding, `@Override`, `super`, Java’s virtual method dispatch, and `final`, and closes with Java enums as named constants.
Key takeaways
- A Java reference’s declared type determines which methods can be called at compile time, while the runtime object determines which overridden implementation executes.
- Upcasts follow known subtype relationships and are compiler-verifiable; downcasts need runtime checks and fail when the actual object is not an instance of the target type.
- A type-hierarchy graph that includes both classes and interfaces helps distinguish a legal runtime-checked cast from an impossible cast between disconnected types.
- Use `@Override` to catch signature mistakes, and use `super.method()` to call a parent implementation without accidentally recursing into the current override.
- Java dynamically dispatches overridden instance methods: a `Pet` reference to a `Dog` still invokes `Dog.noise()`, whereas `final` can prohibit overriding or subclassing.
- A Java `enum` represents named constants such as days of the week and can be used in conditions and `switch` statements.
Chapters
0:00
Compile-Time and Runtime Types in Java Casts
- A reference such as `Person kim1 = new Professor()` has compile-time type `Person` but refers to a runtime `Professor` object.
- Upcasts to a supertype can be verified by the compiler and usually need no explicit cast.
- Downcasts require a runtime check because the referenced object may not actually be an instance of the requested subtype.
9:10
Testing Casts Between Students, Professors, and People
- Casting a `Person` reference that actually refers to a `Professor` into `Professor` succeeds at runtime.
- Casting a `Person` object into `Professor`, or a `Professor` object into sibling type `Student`, produces a runtime cast failure.
- An attempted downcast may compile even when it cannot succeed for the particular object held at runtime.
11:54
Building a Type-Hierarchy Graph for Classes and Interfaces
- The example defines interfaces A, B, and C, with class A implementing interface A.
- Class B extends A and implements interface B; class C extends B and implements interface C.
- Arrows from each subtype to its supertypes make inherited class and interface relationships visible.
15:10
Using the Type Graph to Predict Cast Outcomes
- For a reference declared as interface B and holding a B object, Ghani tests casts to interface A, B, and interface C.
- An upcast is determined from the compile-time type’s direct subtype relationships; the runtime object does not make an otherwise unavailable compile-time upcast.
- A type graph helps distinguish a cast that is a compile-time upcast from one that needs a runtime check.
22:00
Why Some Interface Casts Compile but Fail at Runtime
- A reference with compile-time type interface B cannot be directly upcast to interface A when interface B has no subtype path to interface A.
- If that reference actually holds a B object, casting it to interface A can still succeed at runtime because B implements interface A through its class ancestry.
- Casting a B object to interface C compiles in the connected hierarchy but fails at runtime because B does not implement interface C.
33:29
Disconnected Types Cause Compile-Time Cast Errors
- Ghani explains that a cast can be rejected at compile time when the source and target types have no plausible relationship in the hierarchy.
- A separate class D or isolated interface D has no path connecting it to the A/B/C hierarchy.
- Drawing class and interface relationships helps identify whether a cast needs a runtime check or is impossible to compile.
37:10
Declared Type Controls Method Access
- A variable declared as `Person` can access methods available in `Person`, but not subtype-only methods such as a professor’s `promote()`.
- Even if `Person pat = new Student()` holds a `Student` object, the compiler limits calls through `pat` to the `Person` API.
- Declaring references to a supertype supports flexibility while restricting code to the methods promised by that type.
42:24
Downcasting Emily to Call Professor Methods
- Calling `promote()` through `Person Emily = new Professor()` fails at compile time because `Person` does not declare that method.
- Casting Emily to `Professor` creates a reference that can access professor-specific methods.
- The cast succeeds at runtime because Emily actually refers to a `Professor` object.
46:06
Object Methods and Casting from Java’s Root Type
- Every Java class implicitly extends `Object` when no other superclass is specified.
- `Object` provides methods such as `toString()` and `equals()`, though the default string representation may not be informative.
- A reference declared as `Object` cannot call `Professor` methods until it is cast to `Professor`; that cast works only if the runtime object is a professor.
49:48
Overriding `getName()` for Professor-Specific Output
- A subclass can override an inherited method to provide behavior specific to its type while keeping the shared method name.
- The example has `Professor.getName()` return a professor-prefixed name, while students inherit the ordinary `Person.getName()` behavior.
- Declaring Emily as `Person` while instantiating her as `Professor` keeps the reference general but allows runtime dispatch to professor-specific behavior.
56:36
`@Override`, Access Modifiers, and Calling `super`
- Java’s `@Override` annotation lets the compiler catch misspelled method names that do not actually override a superclass method.
- A subclass cannot access a superclass’s `private` field directly; `protected` permits subclass access, including across packages, while package-private access does not.
- Calling `getName()` from inside its override would recurse; `super.getName()` invokes the parent implementation instead.
1:04:59
Java Virtual Methods Dispatch to the Most Specific Override
- In Java, overridden instance methods are virtual: dispatch uses the runtime object even when the reference is declared as a superclass.
- Both `Pet` and `Dog` references to the same `Dog` object call the overridden `Dog.noise()` method and produce “woof.”
- Dynamic dispatch can add lookup and performance costs; Ghani contrasts it with non-virtual method behavior in other languages.
1:10:47
`final` Prevents Method Overrides and Class Inheritance
- A `final` method cannot be overridden; a subclass that attempts to override it receives a compile-time error.
- A `final` class cannot be extended, preventing subclasses from inheriting from it.
- The keyword therefore restricts either method customization or class inheritance, depending on where it is applied.
1:12:37
Java Enums for Named Constants and Next-Topic Preview
- An `enum` defines a finite set of named constants, illustrated with days of the week such as `Day.Sunday` and `Day.Thursday`.
- Enum values can be tested with `if` statements or handled using `switch` statements.
- Ghani notes enums will be useful in upcoming assignments and previews object relationships as the next lecture topic.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, Muhammad Sayeed Ghani.