Home Polymorphism Dynamic Binding Casting Objects instanceof final Keyword

Week 8: Polymorphism

Liang Chapter 11 — Polymorphism, Dynamic Binding, Casting Objects, instanceof, and the final Keyword

Polymorphism

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)

The Core Idea

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:

GeometricObject
getArea() → ???
Circle
getArea() → π r²
Rectangle
getArea() → w × h
Triangle
getArea() → ½ b × h

Polymorphism in Code

// All three are held in GeometricObject variables GeometricObject shape1 = new Circle(5); GeometricObject shape2 = new Rectangle(3, 4); GeometricObject shape3 = new Triangle(3, 4, 5); GeometricObject[] shapes = { shape1, shape2, shape3 }; for (GeometricObject s : shapes) { System.out.printf("Area: %.2f%n", s.getArea()); // Java calls the correct getArea() for each actual object type } // Output: // Area: 78.54 ← Circle.getArea() // Area: 12.00 ← Rectangle.getArea() // Area: 6.00 ← Triangle.getArea()

Why Polymorphism Matters

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)

Declared Type vs. Actual Type

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.

Q1: What is polymorphism in Java?
A) A superclass copying methods from its subclasses
B) The ability of a superclass variable to refer to subclass objects, with each behaving differently
C) Multiple classes having the same name
D) A subclass having multiple constructors
Polymorphism allows one interface (the superclass type) to be used for many implementations. A GeometricObject variable can hold a Circle, Rectangle, etc., and the correct method is called for each. (Liang, Section 11.7)
Q2: Given GeometricObject g = new Circle(5);, what is the declared type and actual type of g?
A) Declared: Circle, Actual: GeometricObject
B) Declared: GeometricObject, Actual: Circle
C) Both are GeometricObject
D) Both are Circle
The variable 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)

Section Score

0 / 0

Dynamic Binding

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)

Dynamic Binding Example

class Person { public String greet() { return "Hello, I am a person."; } } class Student extends Person { @Override public String greet() { return "Hello, I am a student."; } } class Teacher extends Person { @Override public String greet() { return "Hello, I am a teacher."; } } // In main: Person p1 = new Student(); // declared as Person, actual: Student Person p2 = new Teacher(); // declared as Person, actual: Teacher System.out.println(p1.greet()); // "Hello, I am a student." System.out.println(p2.greet()); // "Hello, I am a teacher."

Dynamic vs. Static Binding

FeatureDynamic BindingStatic Binding
When resolvedAt runtimeAt compile time
Applies toOverridden (instance) methodsstatic, final, private methods
Determined byActual (runtime) typeDeclared (compile-time) type
Also calledLate binding, virtual dispatchEarly binding

Only Instance Methods Use Dynamic 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.

Trace: What does this print?

class Base { public void show() { System.out.println("Base"); } } class Child extends Base { @Override public void show() { System.out.println("Child"); } } // In main: Base obj = new Child(); obj.show();

Hint: The actual type of obj is Child, so Child.show() runs — even though obj is declared as Base.

Q1: Dynamic binding means the method is resolved based on:
A) The declared type of the variable
B) The actual (runtime) type of the object
C) The class that was imported
D) The order of class definitions
In dynamic binding, Java looks at the actual object stored in the variable at runtime to decide which overridden method to call — not the declared type of the variable. (Liang, Section 11.8)
Q2: Which of the following is NOT subject to dynamic binding?
A) An instance method that is overridden in a subclass
B) A static method called through a superclass variable
C) A public overriding method
D) A protected overriding method
static 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)

Section Score

0 / 0

Casting Objects

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)

Upcasting — Always Safe

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).

// Upcasting — implicit, always safe Circle c = new Circle(5.0); GeometricObject g = c; // upcast: Circle → GeometricObject (no cast needed) // g can only call GeometricObject methods: System.out.println(g.getColor()); // OK — from GeometricObject // g.getRadius(); // ERROR — getRadius() is not in GeometricObject API

Downcasting — Explicit, Can Fail

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.

ClassCastException

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)

Upcasting vs. Downcasting Summary

FeatureUpcastingDowncasting
DirectionSubclass → SuperclassSuperclass → Subclass
Cast operatorNot needed (implicit)Required: (SubType)
SafetyAlways safeCan throw ClassCastException
AccessOnly superclass methods visibleAll subclass methods accessible
Best practiceAutomaticAlways check with instanceof first
Q1: What happens when you write GeometricObject g = new Circle(5);?
A) A compile error occurs because types don't match
B) Implicit upcasting — the Circle object is assigned to a GeometricObject variable
C) Explicit downcasting is performed
D) The object is converted into a GeometricObject
This is implicit upcasting — no cast operator needed because Circle is a GeometricObject. The object itself is still a Circle; only the variable type changes. (Liang, Section 11.9)
Q2: GeometricObject g = new Circle(3); Rectangle r = (Rectangle) g; — what happens?
A) Compiles and runs correctly
B) Compile error because Rectangle is not related to Circle
C) Compiles but throws ClassCastException at runtime
D) Silently converts Circle to Rectangle
The compiler allows the cast (both are GeometricObject subclasses), but at runtime the actual object is a Circle, not a Rectangle. Java throws ClassCastException. (Liang, Section 11.9)

Section Score

0 / 0

The instanceof Operator

The 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)

Safe Downcasting Pattern

public static void displayShape(GeometricObject g) { if (g instanceof Circle) { Circle c = (Circle) g; // safe downcast System.out.println("Circle radius: " + c.getRadius()); } else if (g instanceof Rectangle) { Rectangle r = (Rectangle) g; // safe downcast System.out.println("Rectangle width: " + r.getWidth()); } else { System.out.println("Other shape: area = " + g.getArea()); } }

instanceof Rules

ExpressionResultReason
new Circle(3) instanceof CircletrueObject is a Circle
new Circle(3) instanceof GeometricObjecttrueCircle is a GeometricObject (is-a)
new Circle(3) instanceof ObjecttrueEverything extends Object
new Circle(3) instanceof RectanglefalseCircle is not a Rectangle
null instanceof Circlefalsenull is never an instance of anything

Modern Pattern Matching (Java 16+)

Modern Java combines the instanceof check and the cast into one step:

Trace: What does this print?

// Assume Animal is superclass; Dog extends Animal; Cat extends Animal Animal a = new Dog(); System.out.println(a instanceof Animal); System.out.println(a instanceof Dog); System.out.println(a instanceof Cat);

Hint: A Dog is both a Dog and an Animal, but it is not a Cat.

Q1: What does null instanceof String evaluate to?
A) Throws a NullPointerException
B) true — null can be any type
C) false — null is never an instance of any class
D) A compile error
null 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)
Q2: Why should you use instanceof before downcasting?
A) To speed up the program
B) To prevent a ClassCastException at runtime
C) To skip the cast operator entirely
D) To check the declared type of a variable
instanceof 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)

Section Score

0 / 0

The final Keyword

The final keyword can be applied to variables, methods, and classes. In inheritance, it controls what can be extended or overridden. (Liang, Section 11.14)

Three Uses of final

Applied toMeaningExample
VariableValue cannot change (constant)final double PI = 3.14159;
MethodCannot be overridden in a subclasspublic final void lock() {...}
ClassCannot be extended (no subclasses)public final class String {...}

final Method — Cannot Override

final Class — Cannot Extend

Why Use final?

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)

Common Confusion — final vs. static final

final double PI = 3.14; // instance constant — each object has its own PI static final double PI = 3.14; // class constant — shared by all — preferred for true constants

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.

Q1: What does declaring a class as final prevent?
A) Creating instances of the class
B) Other classes from extending (inheriting from) it
C) Calling methods of the class
D) Using the class in an array
A 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)
Q2: Which of the following Java library classes is declared as final?
A) Object
B) ArrayList
C) String
D) Scanner
String, 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)

Section Score

0 / 0

Week 8 Contents

Polymorphism Dynamic Binding Casting Objects instanceof final Keyword