Java course menu
Java Polymorphism: Dynamic Dispatch, Upcasting, instanceof and Casting
Learn polymorphism in Java: treating subclass objects through a superclass or interface type, dynamic method dispatch, upcasting and downcasting, instanceof and pattern matching, ClassCastException, and why polymorphism removes long if-else chains.
On this page
Polymorphism ("many forms") means one piece of code can work with objects of different classes through a common type, and each object still behaves in its own way. A method that pays an Employee can pay managers, interns and contractors without knowing which is which.
Dynamic dispatch#
In Java, when you call an instance method, the JVM picks the implementation from the object's actual class at run time, not from the declared type of the variable:
class Animal {
String sound() { return "..."; }
}
class Dog extends Animal {
@Override
String sound() { return "Woof"; }
}
class Cat extends Animal {
@Override
String sound() { return "Meow"; }
}
public class Main {
static void makeNoise(Animal a) {
System.out.println(a.sound());
}
public static void main(String[] args) {
makeNoise(new Dog());
makeNoise(new Cat());
makeNoise(new Animal());
}
}Woof
Meow
...makeNoise was written once, for Animal, and works for every current and future subclass. Unlike C++, Java methods are polymorphic by default; there is no virtual keyword.
A list of different objects#
The classic use: one collection, many kinds of objects, one loop:
import java.util.List;
class Shape {
double area() { return 0; }
String name() { return "shape"; }
}
class Circle extends Shape {
private final double r;
Circle(double r) { this.r = r; }
@Override double area() { return Math.PI * r * r; }
@Override String name() { return "circle"; }
}
class Rectangle extends Shape {
private final double w;
private final double h;
Rectangle(double w, double h) { this.w = w; this.h = h; }
@Override double area() { return w * h; }
@Override String name() { return "rectangle"; }
}
public class Main {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Rectangle(3, 4), new Circle(2));
double total = 0;
for (Shape s : shapes) {
System.out.printf("%s: %.2f%n", s.name(), s.area());
total += s.area();
}
System.out.printf("Total: %.2f%n", total);
}
}circle: 3.14
rectangle: 12.00
circle: 12.57
Total: 27.71Adding a Triangle later needs only a new class; the loop does not change. Without polymorphism you would need an if (shape is a circle) ... else if (rectangle) ... chain that must be edited for every new shape. In real code, Shape would be abstract or an interface; see the next two lessons.
Upcasting#
Treating a subclass object as its superclass type is upcasting. It is always safe and automatic:
Animal a = new Dog(); // upcast: a Dog is an AnimalThrough a, you can only call methods that Animal declares, even though the object is a Dog. The declared type decides which methods you may call; the actual object decides which implementation runs.
Downcasting and instanceof#
Going back from the general type to the specific one is downcasting. It needs an explicit cast, and fails at run time with a ClassCastException if the object is not really of that type. Check with instanceof first. Since Java 16, pattern matching combines the check and the cast:
import java.util.List;
class Animal {}
class Dog extends Animal {
void fetch() { System.out.println("fetching the ball"); }
}
class Cat extends Animal {}
public class Main {
public static void main(String[] args) {
List<Animal> pets = List.of(new Cat(), new Dog());
for (Animal pet : pets) {
if (pet instanceof Dog dog) { // test and cast in one step
dog.fetch();
} else {
System.out.println("not a dog");
}
}
Animal a = new Cat();
try {
Dog d = (Dog) a; // a Cat is not a Dog
d.fetch();
} catch (ClassCastException e) {
System.out.println("ClassCastException: a Cat cannot be cast to Dog");
}
}
}not a dog
fetching the ball
ClassCastException: a Cat cannot be cast to DogIf you find yourself writing many instanceof checks, the behaviour probably belongs in an overridden method instead.
Polymorphism with Object methods#
toString, equals and hashCode are polymorphic too. System.out.println(x) calls x.toString(), which runs the object's own override:
import java.util.List;
class Ticket {
private final int number;
Ticket(int number) { this.number = number; }
@Override
public String toString() { return "Ticket #" + number; }
}
public class Main {
public static void main(String[] args) {
List<Object> things = List.of(new Ticket(7), "text", 42, 3.5);
for (Object o : things) {
System.out.println(o + " (" + o.getClass().getSimpleName() + ")");
}
}
}Ticket #7 (Ticket)
text (String)
42 (Integer)
3.5 (Double)Overloading is not polymorphism (at run time)#
Overloaded methods are chosen at compile time from the declared types of the arguments, not from the run-time object:
class Animal {}
class Dog extends Animal {}
public class Main {
static String describe(Animal a) { return "an animal"; }
static String describe(Dog d) { return "a dog"; }
public static void main(String[] args) {
Animal pet = new Dog();
System.out.println(describe(pet)); // declared type is Animal
System.out.println(describe(new Dog()));
}
}an animal
a dogOnly overriding dispatches on the actual object.
Common mistakes#
Practice#
Practice exercise
Create a superclass Notification with a method send(String message) that returns a String, and subclasses EmailNotification and SmsNotification that return Email: ... and SMS: .... Put one of each in a List<Notification> and send "Your order has shipped" through all of them.
Show solution
import java.util.List;
class Notification {
String send(String message) {
return "Generic: " + message;
}
}
class EmailNotification extends Notification {
@Override
String send(String message) {
return "Email: " + message;
}
}
class SmsNotification extends Notification {
@Override
String send(String message) {
return "SMS: " + message;
}
}
public class Notify {
public static void main(String[] args) {
List<Notification> channels = List.of(new EmailNotification(), new SmsNotification());
for (Notification channel : channels) {
System.out.println(channel.send("Your order has shipped"));
}
}
}Email: Your order has shipped
SMS: Your order has shippedKey takeaway#
Finished this lesson?