Liang Chapter 11 — Polymorphism, Dynamic Binding, Casting Objects, instanceof, and the final Keyword
Polymorphism means "many forms." In Java, a variable of a superclass type can hold a reference to an object of any subclass. The same method call on different objects produces different behavior — each object responds in its own way. (Liang, Section 11.7)
Suppose GeometricObject is the superclass, and Circle, Rectangle, and Triangle are subclasses. You can write a single variable of type GeometricObject that holds any of them:
Write code once against the superclass type, and it automatically works for all current and future subclasses. No need for if/else chains to check the type. This is the foundation of extensible object-oriented design. (Liang, Section 11.7)
The declared type is what the variable is declared as (GeometricObject). The actual type (runtime type) is what object it actually holds (Circle). Polymorphism works because Java uses the actual type to decide which method to call.
GeometricObject variable can hold a Circle, Rectangle, etc., and the correct method is called for each. (Liang, Section 11.7)GeometricObject g = new Circle(5);, what is the declared type and actual type of g?g is declared as GeometricObject (declared type). The object it actually holds at runtime is a Circle (actual type). Java uses the actual type for method dispatch. (Liang, Section 11.7)When a method is invoked on a variable, Java determines which implementation to run based on the actual type of the object at runtime — not the declared type of the variable. This is called dynamic binding (or late binding). (Liang, Section 11.8)
| Feature | Dynamic Binding | Static Binding |
|---|---|---|
| When resolved | At runtime | At compile time |
| Applies to | Overridden (instance) methods | static, final, private methods |
| Determined by | Actual (runtime) type | Declared (compile-time) type |
| Also called | Late binding, virtual dispatch | Early binding |
static methods are NOT dynamically bound — they are resolved based on the declared type, not the actual type. Calling a static method through a superclass variable always runs the superclass version.
Hint: The actual type of obj is Child, so Child.show() runs — even though obj is declared as Base.
static method called through a superclass variablepublic overriding methodprotected overriding methodstatic methods are resolved at compile time (static binding) based on the declared type. They cannot be overridden — only hidden. private and final methods are also statically bound. (Liang, Section 11.8)You can cast object references between related types in an inheritance hierarchy. Upcasting (subclass → superclass) is always safe and implicit. Downcasting (superclass → subclass) is explicit and can fail at runtime. (Liang, Section 11.9)
Assigning a subclass object to a superclass variable. Java does this implicitly — no cast operator needed. You lose access to subclass-specific methods (only the superclass API is available through the variable).
Assigning a superclass variable back to a subclass variable. Requires an explicit cast operator (SubclassType). If the actual object is not of that type, Java throws a ClassCastException at runtime.
A ClassCastException is thrown at runtime when you downcast an object to a type it does not actually belong to. Always use instanceof before downcasting to avoid this error. (Liang, Section 11.9)
| Feature | Upcasting | Downcasting |
|---|---|---|
| Direction | Subclass → Superclass | Superclass → Subclass |
| Cast operator | Not needed (implicit) | Required: (SubType) |
| Safety | Always safe | Can throw ClassCastException |
| Access | Only superclass methods visible | All subclass methods accessible |
| Best practice | Automatic | Always check with instanceof first |
GeometricObject g = new Circle(5);?Circle is a GeometricObject. The object itself is still a Circle; only the variable type changes. (Liang, Section 11.9)GeometricObject g = new Circle(3); Rectangle r = (Rectangle) g; — what happens?ClassCastException at runtimeGeometricObject subclasses), but at runtime the actual object is a Circle, not a Rectangle. Java throws ClassCastException. (Liang, Section 11.9)instanceof OperatorThe instanceof operator tests whether an object is an instance of a particular class (or its subclasses). Use it before downcasting to prevent ClassCastException. (Liang, Section 11.9)
| Expression | Result | Reason |
|---|---|---|
new Circle(3) instanceof Circle | true | Object is a Circle |
new Circle(3) instanceof GeometricObject | true | Circle is a GeometricObject (is-a) |
new Circle(3) instanceof Object | true | Everything extends Object |
new Circle(3) instanceof Rectangle | false | Circle is not a Rectangle |
null instanceof Circle | false | null is never an instance of anything |
Modern Java combines the instanceof check and the cast into one step:
Hint: A Dog is both a Dog and an Animal, but it is not a Cat.
null instanceof String evaluate to?NullPointerExceptiontrue — null can be any typefalse — null is never an instance of any classnull instanceof <AnyType> always returns false — it does not throw an exception. This is a safe check even when the reference might be null. (Liang, Section 11.9)instanceof before downcasting?ClassCastException at runtimeinstanceof verifies the actual runtime type before you cast. If the check passes, the downcast is guaranteed to succeed. Without it, a wrong downcast throws ClassCastException. (Liang, Section 11.9)final KeywordThe final keyword can be applied to variables, methods, and classes. In inheritance, it controls what can be extended or overridden. (Liang, Section 11.14)
| Applied to | Meaning | Example |
|---|---|---|
| Variable | Value cannot change (constant) | final double PI = 3.14159; |
| Method | Cannot be overridden in a subclass | public final void lock() {...} |
| Class | Cannot be extended (no subclasses) | public final class String {...} |
Use final on a class when it represents a complete, well-defined concept that shouldn't be extended (e.g., String, Integer). Use final on a method to guarantee specific security or correctness behavior that subclasses must not alter. (Liang, Section 11.14)
True constants should be static final — declared once for the class. A plain final instance variable is set once per object but takes memory per instance.
final prevent?final class cannot be subclassed. You can still create instances of it. java.lang.String is a classic example — no class can extend String. (Liang, Section 11.14)final?ObjectArrayListStringScannerString, Integer, Double, and Math are all declared final in the Java API. Object is the root of the hierarchy and is not final. (Liang, Section 11.14)