Polymorphism
By the end of this lesson, you'll be able to:
- Explain polymorphism as treating different types uniformly through a shared type
- Loop over a collection of a parent/interface type containing several subclasses
- Recognize why polymorphism reduces the need for type-checking branches
Prerequisites: "Interfaces"
One list, several actual types
List<Shape> shapes = new List<Shape>{
new Circle(),
new Rectangle()
};
for (Shape shape : shapes) {
System.debug(shape.calculateArea());
}
List<Shape> shapes = new List<Shape>{— typed asList<Shape>, but about to hold a mix of actual types.new Circle(),— aCircleobject.new Rectangle()— aRectangleobject.};— closes the list literal.- (blank line)
for (Shape shape : shapes) {— loops treating every element as aShape, regardless of its real type.System.debug(shape.calculateArea());— callscalculateArea()without ever checking which one it is — each object runs its own overridden version automatically.}— closes the loop.
This is polymorphism: "many forms" behind one shared type.
What this replaces: manual type-checking
// The version WITHOUT polymorphism — fragile and grows worse over time
for (Object shapeObj : untyped) {
if (shapeObj instanceof Circle) {
Circle c = (Circle) shapeObj;
System.debug(Math.PI * c.radius * c.radius);
} else if (shapeObj instanceof Rectangle) {
Rectangle r = (Rectangle) shapeObj;
System.debug(r.width * r.height);
}
// every new shape type means editing this method again
}
// The version WITHOUT polymorphism...— a warning label for what follows.for (Object shapeObj : untyped) {— loops over untyped objects, so each one's real type must be checked manually.if (shapeObj instanceof Circle) {—instanceofchecks the object's actual runtime type.Circle c = (Circle) shapeObj;— casts it toCirclebefore its fields can be used.System.debug(Math.PI * c.radius * c.radius);— repeatsCircle's formula inline, right here in the loop.} else if (shapeObj instanceof Rectangle) {— checks for the other known type.Rectangle r = (Rectangle) shapeObj;— casts it toRectangle.System.debug(r.width * r.height);— repeatsRectangle's formula inline too.}— closes theif/else ifchain.// every new shape type means editing this method again— every new shape type means coming back to add anotherelse ifbranch here.}— closes the loop.
With polymorphism, adding a new Shape subclass requires zero changes to a loop like the one above — it already knows how to call calculateArea() on anything that's a Shape.
A real business example: Payroll
List<Payable> payees = new List<Payable>{
new Contractor(),
new SalariedEmployee()
};
Decimal totalPayout = 0;
for (Payable payee : payees) {
totalPayout += payee.calculatePayment();
}
List<Payable> payees = new List<Payable>{— a list of the shared interface type.new Contractor(),— one kind of payee.new SalariedEmployee()— a completely different kind of payee.};— closes the list literal.- (blank line)
Decimal totalPayout = 0;— running total, starting at zero.for (Payable payee : payees) {— loops treating every element asPayable.totalPayout += payee.calculatePayment();— doesn't need to know or care whether it's paying a contractor or a salaried employee — both fulfilPayable.}— closes the loop.
One loop handles the entire payroll regardless of how many different payee types exist.
Exercise
Given the Payable interface and Contractor class from the previous lesson, plus a second class SalariedEmployee implementing Payable (returning a fixed 8000), write a loop over a List<Payable> containing one of each, debugging each calculatePayment() result.
Show hint
for (Payable p : payees) { System.debug(p.calculatePayment()); }
Polymorphism Quiz
My Notes
Log in to keep private notes on this lesson.
Questions about this lesson
No questions yet — be the first to ask.
Log in to ask a question about this lesson.
Summary
Polymorphism means code can work with several different types uniformly, as long as they share a parent class or interface — without needing to know or check which specific type it's dealing with.