Skip to main content
Lecture 2 Cover

CS 3100: Program Design and Implementation II

Lecture 2: Inheritance and Polymorphism in Java

©2026 Jonathan Bell & Ellen Spertus, CC-BY-SA

Learning Objectives

After this lecture, you will be able to:

  1. Describe why inheritance is a core concept of OOP
  2. Define a type hierarchy and understand superclass/subclass relationships
  3. Explain the role of interfaces and abstract classes
  4. Describe the JVM's implementation of dynamic dispatch
  5. Describe the difference between static and instance methods
  6. Describe the JVM exception handling mechanism
  7. Recognize common Java exceptions and when to use them

Can students complete polls when outside of the classroom?

A. No, it's physically impossible.

B. Yes, and you should complete polls whether or not you're physically present.

C. Yes, but it's an academic integrity violation.

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Ellen's Office Hours

  • Mondays and Thursdays, 3-4 PM, online and in CPM 201
  • Other days by appointment

Would you attend in-person office hours?

A. Yes, often

B. Occasionally

C. Rarely, if ever

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Software Models Real-World Domains

Split comparison: meaningless database columns vs meaningful domain model types

Example Domain: Smart Home IoT

Let's design a system to control smart home devices

What concepts exist in this domain?

  • Devices (lights, fans, thermostats, sensors...)
  • Rooms, zones
  • Schedules, automations
  • Users, permissions

Today we'll focus on devices

Real-World Concepts Form Natural Hierarchies

In the real world, concepts have natural relationships:

  • A dimmable light is a kind of light
  • A light is a kind of device
  • A fan is a kind of device

These "is-a" relationships form a hierarchy

Inheritance Captures "Is-A" Relationships

Inheritance is a core OOP concept that lets us model "is-a" relationships

  • Child types inherit behavior from parent types
  • Child types can extend with new capabilities
  • Child types can override to specialize behavior

Bonus: Avoid duplicating code → easier to maintain

Not How Inheritance Is Supposed To Work

Meme about someone inheriting their mother's code.

Y2K Bug

Dilbert cartoon

Base Types Define Shared Capabilities

Every device in our system shares some basic capabilities:

  • identify() — help a human find the device (e.g., flash a light, wiggle a shade or fan)
  • isAvailable() — check if the device is reachable
  • + — indicates that a method is public
  • italics — indicates a method is abstract (not implemented)

Subtypes Add Specialized Behavior

A skeletal implementation provides shared behavior; subclasses specialize:

Discussion: Why Separate Types?

Lights and fans both have on/off behavior...

Why not just use one SwitchableDevice type?

Think about: What does the domain tell us? How would users think about these?

Hierarchies Can Go Multiple Levels Deep

Not all lights are the same — some have more features:

The Complete Hierarchy

Each Level Inherits Everything Above It

TypeInherits FromAdds
IoTDeviceidentify(), isAvailable()
BaseIoTDeviceIoTDeviceImplements isAvailable(), adds deviceId
LightBaseIoTDeviceturnOn(), turnOff(), isOn()
DimmableLightLightsetBrightness(), getBrightness()
TunableWhiteLightDimmableLightsetColorTemperature(), getColorTemperature()

Each level inherits everything from above and adds new capabilities

Test Your Understanding: Animal

Where should getSpecies() be implemented (not abstract)?

Cat cat = new Cat();
System.out.println(cat.getSpecies()); // Felis catus
Dog dog = new Dog();
System.out.println(dog.getSpecies()); // Canis familiaris

Animal Class Diagram and Code 1

UML and code for Animal class

Your Code Should Tell a Story About the Domain

  • Understand the domain before writing code
  • "Is-a" relationships in the domain → inheritance in code
  • Common behavior goes in parent types
  • Specialized behavior goes in child types
  • Good names come from domain vocabulary

Animal Class Diagram and Code 2

UML and code for Animal class

Sketch Your Types Before You Code

Universal adapter testing station showing subtypes being tested for substitutability

The Liskov Substitution Principle

"If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program."

— Barbara Liskov, 1987

Java Uses Classes and Interfaces for Hierarchies

Every language represents type hierarchies differently

Java uses two constructs:

Classes
Concrete implementations
Interfaces
Behavioral contracts

Classes Extend One Superclass, Implement Many Interfaces

  • Can extend exactly one superclass
  • Can implement multiple interfaces
  • Inherit fields and methods from superclass
  • Can override inherited methods
  • Can be concrete or abstract

Defining a Class

public class TunableWhiteLight extends DimmableLight {
private int startupColorTemperature;

public TunableWhiteLight(String deviceId, int colorTemp, int brightness) {
super(deviceId, brightness); // Call parent constructor
this.startupColorTemperature = colorTemp;
}

@Override
public void turnOn() {
setColorTemperature(startupColorTemperature);
super.turnOn(); // Call parent's turnOn
}

public void setColorTemperature(int colorTemperature) { /* ... */ }
public int getColorTemperature() { /* ... */ }
}

Key Syntax Points

extendsSpecifies the superclass
super(...)Call the parent's constructor (must be first line)
@OverrideAnnotation indicating method override
super.method()Call the parent's version of a method
this.fieldRefers to instance field (not parameter)

Visibility Controls Who Can Access Your Code

ModifierAccessible By
publicAny class
protectedSame class, subclasses, same package
privateSame class only
(none)Package-private — avoid!

Always specify visibility explicitly

Subtypes Must Behave Like Their Supertypes

Liskov Substitution Principle illustrated as socket compatibility testing

Subclasses Work Wherever Supertypes Are Expected

Light[] lights = new Light[2];
TunableWhiteLight light1 = new TunableWhiteLight("light-1", 2700, 100);
lights[0] = light1; // ✓ TunableWhiteLight IS-A Light

DimmableLight light2 = new DimmableLight("light-2", 100);
lights[1] = light2; // ✓ DimmableLight IS-A Light

for (Light light : lights) {
light.turnOn(); // Works for any Light!
}

A subclass can always be used where a superclass is expected

Downcasting Accesses Subclass-Specific Features

To access subclass-specific methods, cast the reference:

// Explicit cast to access subclass method
TunableWhiteLight twl = (TunableWhiteLight) lights[0];
twl.setColorTemperature(2200);

// Or inline:
((TunableWhiteLight) lights[0]).setColorTemperature(2200);

⚠️ Throws ClassCastException if the cast is invalid!

Interfaces Define Contracts Without Implementation

The Universal Charging Station — Interfaces as Standardized Contracts. A clean, modern illustration showing a universal charging station with multiple USB-C ports, rendered in a technical-but-friendly blueprint style. LEFT SIDE - THE INTERFACE (USB-C Port): A large, detailed USB-C port labeled "Chargeable" with its specification card showing: charge(), getCapacity(), getBatteryLevel(). A sign reads: "Any device implementing this interface can charge here." The port itself is highlighted with a soft glow, emphasizing it's a SPECIFICATION, not a device. RIGHT SIDE - THE IMPLEMENTATIONS: Three very different devices successfully plugged into identical ports: (1) A smartphone labeled "Phone implements Chargeable" — its charge() uses fast-charging circuitry; (2) A laptop labeled "Laptop implements Chargeable" — its charge() manages multiple battery cells; (3) A wireless earbud case labeled "Earbuds implements Chargeable" — its charge() trickle-charges tiny batteries. Each device has a small annotation showing its internal implementation is completely different (different battery sizes, charging speeds, circuits), but they all present the same interface to the charging station. BOTTOM - THE KEY INSIGHT: A callout box states: "The charging station doesn't know or care HOW each device charges. It only knows they all honor the Chargeable contract." Visual style: Use a color scheme that distinguishes the interface (blue/purple, abstract, glowing) from implementations (varied colors, concrete, detailed). The USB-C port should look standardized and precise. The devices should look diverse in design but unified by the connection point. Include small method signatures floating near each device showing their different internal approaches to charge(). Perhaps show a rejected device (old proprietary charger) that doesn't fit—illustrating that non-implementing classes can't participate. Tagline: "One contract, many implementations."

  • Define a contract — methods a class must implement
  • A class can implement multiple interfaces
  • Cannot be instantiated directly
  • No instance fields (only constants)

Defining an Interface

public interface IoTDevice {
/** Identify the device to a human (flash, spin, beep). */
void identify();

/** Check if the device is available. */
boolean isAvailable();
}

Note: public is optional in interface methods (always public)

Abstract Classes Provide Skeletal Implementations

A skeletal implementation provides shared behavior while leaving specifics abstract:

public abstract class BaseIoTDevice implements IoTDevice {
protected String deviceId;
protected boolean isConnected;

public BaseIoTDevice(String deviceId) {
this.deviceId = deviceId;
}

@Override
public boolean isAvailable() { return this.isConnected; }

public abstract void identify(); // Subclasses must implement
}

Use Interfaces for Contracts, Abstract Classes for Shared State

InterfaceAbstract Class
Multiple inheritanceYesNo
Instance fieldsNoYes
Concrete methodsDefault onlyYes
ConstructorNoYes

Rule of thumb: prefer interfaces; use abstract classes when you need shared state or implementation

The JVM Chooses Methods at Runtime, Not Compile Time

How does the JVM know which method to call?

Light[] lights = new Light[] {
new TunableWhiteLight("light-1", 2700, 100),
new DimmableLight("light-2", 100)
};

for (Light light : lights) {
light.turnOn(); // Which turnOn() is called?
}

Method Lookup Walks Up the Type Hierarchy

To call method m on object o of runtime type T:

  1. If T declares m, use that
  2. Otherwise, check T's superclass recursively
  3. If still not found, use default interface method (if any)

Runtime type matters, not compile-time type!

Which Method Gets Called?

Call:twl.turnOn()

TunableWhiteLight twl =
new TunableWhiteLight("living-room", 2700, 100);
twl.turnOn();

Runtime type:TunableWhiteLight

Step 1: Does TunableWhiteLight declare turnOn()?

✓ Yes! Use TunableWhiteLight.turnOn()

Which Method Gets Called?

Call:twl.setBrightness(50)

TunableWhiteLight twl =
new TunableWhiteLight("living-room", 2700, 100);
twl.setBrightness(50);

Runtime type:TunableWhiteLight

Step 1: Does TunableWhiteLight declare setBrightness()? No

Step 2: Check superclass DimmableLight...

✓ Found! Use DimmableLight.setBrightness()

Which Method Gets Called?

Call:light.turnOn()

Light light =
new TunableWhiteLight("living-room", 2700, 100);
light.turnOn();

Compile-time type:Light

Runtime type:TunableWhiteLight

Step 1: Does TunableWhiteLight declare turnOn()?

✓ Yes! Use TunableWhiteLight.turnOn()

The variable type (Light) doesn't matter — runtime type does!

Poll: What does this do? 1

abstract class Animal {
public abstract String getSpecies();
}

class Cat extends Animal {
@Override
public String getSpecies() {
return "Felis catus";
}

public static void main(String[] args) {
Animal animal = new Cat();
System.out.println(animal.getSpecies());
}
}

A. nothing

B. prints "Felis catus"

C. does not compile

D. has runtime error

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Poll: What does this do? 2

abstract class Animal {
public abstract String getSpecies();
}

class Cat extends Animal {
@Override
public String getSpecies() {
return "Felis catus";
}

public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.getSpecies());
}
}

A. nothing

B. prints "Felis catus"

C. does not compile

D. has runtime error

Poll Everywhere QR Code or Logo

Text espertus to 22333 if the
URL isn't working for you.

https://pollev.com/espertus

Casting Doesn't Change Which Method Runs

Light light = new TunableWhiteLight("living-room", 2700, 100);

light.turnOn();
// Calls TunableWhiteLight.turnOn()

((DimmableLight) light).turnOn();
// STILL calls TunableWhiteLight.turnOn()!

The cast doesn't change which method is called

The actual object type at runtime determines the method

Instance Methods Dispatch Dynamically; Static Methods Don't

Instance Methods

  • Belong to an object
  • Can access this
  • Dynamically dispatched

Static Methods

  • Belong to the class
  • No this reference
  • Statically bound

Static Methods Belong to Classes, Not Objects

public class TunableWhiteLight extends DimmableLight {
// ...

/**
* Convert color temperature from Kelvin to mireds.
*/
public static int degreesKelvinToMired(int degreesKelvin) {
return 1000000 / degreesKelvin;
}
}

// Invocation — use the class name, not an instance
int mireds = TunableWhiteLight.degreesKelvinToMired(2700);

No object needed — method is resolved at compile time

Some Objects Belong to Multiple Categories

What if a class has more than one supertype? For example, a Cat is both an Animal and a FuzzyThing.

interface Animal {
void feed();
}

interface FuzzyThing {
void shed();
}

class Cat implements Animal, FuzzyThing {
public void feed() {
System.out.println("Nom nom nom");
}
public void shed() {
System.out.println("Hair everywhere!");
}
}

Can Cat be a subtype of both? Yes! A class can implement multiple interfaces.

Multiple Inheritance

Can a class be a subtype of two classes?

abstract class Animal {
boolean causesAllergies() {
return false; // most animals don't
}
}

abstract class FuzzyThing {
boolean causesAllergies() {
return true; // fuzzy things do
}
}

// NOT ALLOWED IN JAVA!
class Cat extends Animal, FuzzyThing {
// Which causesAllergies() do we inherit?
}

Which implementation would Cat use?

To avoid this problem, Java disallows multiple inheritance. (C++ and Python permit it.)

Real Objects Often Have Multiple "Is-A" Relationships

Back to our IoT domain... what about a ceiling fan with a light?

It's both a light AND a fan!

More Examples of Multiple Inheritance

Real-world objects often have multiple "is-a" relationships:

  • A smart thermostat is both a temperature sensor and a controllable device
  • A smartphone is a phone, a camera, and a computer
  • A teaching assistant is both a student and an employee
  • An amphibious vehicle is both a boat and a car

In each case, the object legitimately belongs to multiple categories

Multiple Class Inheritance Creates Ambiguity

Why don't most languages allow multiple class inheritance?

Which identify() does CeilingFanWithLight inherit?

The Compiler Can't Choose Between Conflicting Implementations

Conflicting implementations visualization

No Multiple Inheritance

Meme about multiple inheritance: And when everyone's Super...No one will be.

Java Avoids the Diamond Problem with Interfaces

Java avoids the diamond problem by restricting multiple inheritance to interfaces:

  • Interfaces don't provide implementations (usually)
  • No ambiguity about which implementation to inherit
  • The implementing class must provide the implementation
public class CeilingFanWithLight implements Light, Fan {
@Override
public void identify() {
// We decide: flash AND spin!
flashLight();
spinBlades();
}
// ... implement all other methods from both interfaces
}

Composition: "Has-A" Instead of "Is-A"

Instead of being both types, contain instances of them

Composition Lets You Control How Behaviors Combine

With composition, we decide how to combine behaviors:

public class CeilingFanWithLight implements IoTDevice {
private Light light; // has-a light
private Fan fan; // has-a fan

@Override
public void identify() {
// No ambiguity — we explicitly delegate to both!
light.identify(); // flashes the light
fan.identify(); // wiggles the blades forward/backward
}

public void turnOnLight() { light.turnOn(); }
public void setFanSpeed(int speed) { fan.setSpeed(speed); }
}

✓ Diamond problem solved — we control the delegation

Composition Trades Substitutability for Flexibility

Advantages

  • No diamond problem
  • Can swap implementations at runtime
  • More flexible coupling
  • Easier to test (mock components)

Disadvantages

  • Can't substitute for Light or Fan
  • Must manually delegate methods
  • More boilerplate code

"Prefer composition over inheritance" — but know when inheritance fits better

Combine Interfaces and Composition for Maximum Flexibility

Best of both worlds: composition with interface implementation

public class CeilingFanWithLight implements Light, Fan, IoTDevice {
private Light lightComponent; // Delegate to this
private Fan fanComponent; // Delegate to this

@Override
public void identify() {
lightComponent.identify();
fanComponent.identify();
}

@Override
public void turnOn() { lightComponent.turnOn(); }

@Override
public void setSpeed(int speed) { fanComponent.setSpeed(speed); }
// ... other delegated methods
}

✓ Can substitute for Light, Fan, and IoTDevice!

Java's Exception Hierarchy Distinguishes Error Types

All exceptions extend Throwable

Checked Exceptions Must be Declared

Airport customs: green channel for unchecked exceptions, red channel for checked exceptions
public void turnOn() throws IOException {
// ...
}

Errors Are Fatal; Exceptions Are Recoverable

ErrorException
Typically fatalRecoverable
JVM-detected problemsApplication-level problems
OutOfMemoryErrorIOException
StackOverflowErrorNullPointerException

Don't throw Error in your code!

Checked Exceptions Force Callers to Handle Failure

CheckedUnchecked
Must be caught or declaredNo requirement to handle
Subclass of ExceptionSubclass of RuntimeException
IOExceptionNullPointerException
SQLExceptionIllegalArgumentException

Python: all exceptions are unchecked

Prefer Standard Exceptions Over Custom Ones

IllegalArgumentExceptionInvalid method argument
NullPointerExceptionUnexpected null value
IllegalStateExceptionObject in wrong state for operation
IndexOutOfBoundsExceptionIndex outside valid range
UnsupportedOperationExceptionOperation not supported

Prefer standard exceptions over custom ones!

Fail Fast: Check Parameters and Throw Clear Exceptions

/**
* Set the color temperature of the light.
* @param colorTemperature Temperature in degrees Kelvin.
* @throws IllegalArgumentException if temperature is out of range.
*/
public void setColorTemperature(int colorTemperature) {
if (colorTemperature < 1000 || colorTemperature > 10000) {
throw new IllegalArgumentException(
"Color temperature must be between 1,000 and 10,000 Kelvin");
}
// ... proceed with valid input
}

Check parameters early — fail fast with clear messages

⚠️ Exceptions Are for Exceptional Cases

// DON'T DO THIS!
try {
int i = 0;
while (true) {
lights[i].turnOn();
i++;
}
} catch (ArrayIndexOutOfBoundsException e) {
// Using exception for flow control — bad!
}
// DO THIS INSTEAD
for (int i = 0; i < lights.length; i++) {
lights[i].turnOn();
}

Summary

  • Inheritance models "is-a" relationships & enables code reuse
  • Interfaces define contracts; abstract classes share implementation
  • Multiple inheritance: desirable but causes diamond problem; Java uses interfaces
  • Liskov Substitution: subtypes must be substitutable for supertypes
  • Dynamic dispatch: runtime type determines which method runs
  • Exceptions: checked vs unchecked; favor standard exceptions

Next Steps

Questions?