What is Polymorphism: An Introduction with Examples in Java and Python
In this post, we will introduce the object-oriented programming concept of polymorphism using examples in Java and Python.
What is Polymorphism?
Polymorphism is the ability of an object to assume multiple forms. For example, a motorbike is some type of bike. It is also some type of motorized vehicle. The motorbike is polymorphic because it appears in the form of a motorized vehicle and in the form of a bike. In object-oriented programming, polymorphism is closely tied to the notion of an interface. You can check if an object is polymorphic by asking whether the object is also an object of another type. In Java, all objects are polymorphic because in addition to being objects of their own class they are also general Java objects.
What is an Interface?
An interface defines a set of behaviors that an object needs to have. For example, you could create a Bike interface that defines that an object conforming to Bike needs to have an attribute called “number of wheels”, an “accelerate” method, and a “stop” method.
What is Polymorphism in Java?
Java polymorphism can be distinguished in runtime polymorphism and compile-time polymorphism. Runtime polymorphism means that a call to a method is resolved at runtime instead of compile-time, while compile-time polymorphism means that a call is resolved at compile-time
Runtime Polymorphism in Java
In the post on inheritance, we’ve already discussed how a subclass can override a method from its superclass. This is a classic example of runtime polymorphism because the methods in the subclass and the superclass have the same name and signature. They are indistinguishable to the compiler. This means you can pass a subclass to a method that expects a superclass. Thus, the concept of polymorphism is closely tied to inheritance. In the following example, we will look at how polymorphism works in the context of an interface. But the same principles also apply to classes.
An interface in Java takes polymorphism further than a class by only defining the method signature without implementing it. This is a very powerful feature that makes Java very flexible. If you don’t know yet what exact object you will pass to a method, you simply define an interface that specifies the requirements the object needs to have. At runtime, you can pass any object which conforms to the requirements specified in the interface.
We can define an interface Bike that requires methods for accelerating and stopping.
interface Bike { void accelerate(); void stop(); }
Next, we define a Motorbike class and make it conform to the bike interface by using the Java implements keyword. Immediately, the compiler will give us the following error.
The compiler forces us to implement the methods defined in the interface. This makes sense since it would defeat the purpose of an interface if you could choose to not implement the required methods.
So let’s implement the methods:
public class Motorbike implements Bike { private int speed = 0; @Override public void accelerate(){ speed += 20; System.out.println("Accelerate Motorbike. Current Speed: "); System.out.println(this.speed); } @Override public void stop() { speed = 0; System.out.println("Stop Motorbike. Current Speed: "); System.out.println(this.speed); } }
If you are using an IDE like IntelliJ, you usually have the option of letting the IDE implement the required methods for you. Now there exists an is-a relationship. Motorbike also is an object of type Bike.
To see how this works in action, let’s define another class Rider who can ride any type of bike using a “rideBike” method that accepts any object conforming to the Bike interface.
public class Rider { public void rideBike(Bike bike){ bike.accelerate(); bike.stop(); } }
We instantiate and pass a motorbike to the rideBike function.
When executing this code, the printouts in the console tell us that this is a motorcycle. But we didn’t know this beforehand. At compile time the rider accepts any object conforming to the Bike interface.
To illustrate this, we define another class Bicycle that also conforms to Bike.
public class Bicycle implements Bike { private int speed = 0; @Override public void accelerate(){ speed += 5; System.out.println("Accelerate Bicycle. Current Speed: "); System.out.println(this.speed); } @Override public void stop() { speed = 0; System.out.println("Stop Bicycle. Current Speed: "); System.out.println(this.speed); } }
And we pass it to the rideBike method.
Now, the printouts tell us that this is a bicycle object.
Compile Time Polymorphism and Method Overloading in Java
Static polymorphism, also known as compile time polymorphism, in Java is achieved by method overloading.
What is Method Overloading in Java?
Method overloading happens when you create more than one method of the same name but give each of them a different number, type, or order of parameters. That way the compiler can distinguish the methods before you execute the code.
In our Motorbike class, we can define a second accelerate method, that takes the desired speed as an argument.
public class Motorbike implements Bike { private int speed = 0; @Override public void accelerate(){ speed += 20; System.out.println("Accelerate Motorbike. Current Speed: "); System.out.println(this.speed); } public void accelerate(int speed) { this.speed = speed; System.out.println("Accelerate Motorbike. Current Speed: "); System.out.println(this.speed); } }
The first method always increases the current speed by a default value of 20 while the second method always sets the speed exactly according to the number passed by the user. Thus, if we first call the second accelerate method with a value of 50, the speed should be 50. If we subsequently call the first method, it will add 20 to the current speed of 50 setting the total speed to 70.
Polymorphism in Python
Since Python is an interpreted language, we don’t distinguish between runtime polymorphism and compile time polymorphism. Furthermore, Python does not require the type of an object or an appropriate interface to be defined before passing an object to a function. Instead, Python relies on duck typing. This means as long as the receiving function can call the methods on the object that it wants to call, it assumes that you are passing the correct object without the need to explicitly verify its type. If it walks like a duck, and quacks like a duck, it probably is a duck.
Let’s define two classes Bicycle and Motorcycle that have a method “accelerate”.
class Bicycle: def __init__(self): self.speed = 0 def accelerate(self): self.speed += 5 print("Accelerate Bicycle. Current Speed: ") print(self.speed) class Motorbike(): def __init__(self): self.speed = 0 def accelerate(self): self.speed += 20 print("Accelerate Motorbike. Current Speed: ") print(self.speed)
In addition we create a ride_bike function which calls the accelerate function on whatever object you pass to it.
def ride_bike(bike): bike.accelerate()
If we create the motorbike and the bicycle objects, we can pass the straight the the ride bike method. Contrary to Java and other statically typed languages, there is no need to define a common interface which requires an implementation of the accelerate method.
If we have an inheritance hierarchy where we have a superclass that provides a default implementation for a method, we do not necessarily need the subclass to provide its custom implementation. For example, if we have a superclass bike that defines the accelerate method, the subclass motorbike does not need to implement this method.
class Bike: def __init__(self): self.speed = 0 def accelerate(self): self.speed += 1 print("Accelerate Bike. Current Speed: ") print(self.speed) class Motorbike(Bike): def __init__(self): Bike.__init__(self) self.speed = 0
If we call the ride_bike function on the motorbike object, it will apply the accelerate method from the Bike superclass.
Of course, we can also provide our own implementation of “accelerate” in the motorbike class, which would override the same method in the bike class.
class Motorbike(Bike): def __init__(self): Bike.__init__(self) self.speed = 0 def accelerate(self): self.speed += 20 print("Accelerate Motorbike. Current Speed: ") print(self.speed)
Method Overloading in Python
In Java we can use method overloading to achieve static polymorphism. Python is a dynamically typed language without compiler feedback and it doesn’t support method overloading out of the box.
If we try to define multiple methods of the same name in the Motorbike class, Python can only understand the method that was defined last.
class Motorbike(Bike): def __init__(self): Bike.__init__(self) self.speed = 0 def accelerate(self): self.speed += 20 print("Accelerate Motorbike. Current Speed: ") print(self.speed) def accelerate(self, speed): self.speed += speed print("Accelerate Motorbike. Current Speed: ") print(self.speed)
We’ve defined the accelerate function with an argument for speed last. If we call the first implementation that does not take speed as an argument, Python throws a type error.
There is a workaround, though, using default arguments.
def accelerate(self, speed = None): if(speed == None): self.speed += 20 else: self.speed += speed print("Accelerate Motorbike. Current Speed: ") print(self.speed)
Now we can call the accelerate method either with or without the “speed” argument.