PolymorphismS2C Home « Polymorphism
In this lesson we continue our investigation of OO concepts by learning about polymorphism, which is formed from the Greek roots of the terms poly meaning many and morph meaning change or form, which roughly translated means the ability to assume different forms. So what does this mean for us Java programmers? To explain this we will refresh our memory about how we create an object:
So lets go through the three step process above:
- Declaration - Here. we declare a reference type named
garbage
of typeGarbage
and theJVM
allocates space for it. - Creation - Tells the
JVM
to allocate space on The Heap for a newGarbage
object. - Assignment - Assign the new
Garbage
object to the reference typegarbage
.
The reference type and the object type are the same.
Polymorphic Method Invocation Top
With polymorphism when you declare the reference type in the declaration, it can be from a superclass of the object we are creating. Or looking from a different perspective, when creating an object, we can use any supertype from a class that we have extended. The compiler has no problem with this but the power of polymorphism happens at runtime through the concept of virtual method invocation. This concept is the dynamic selection of overridden methods at runtime based on the actual object type, rather than the reference type. This only applies to instance methods, everything else uses the reference type at runtime.
So what does this give us I hear you ask? Well we can use the supertype for reference type declaration when instantiating our objects, safe in the knowledge that the JVM
will use the actual object created to
invoke the correct overridden methods of the subtypes at runtime. This ties in with the concept of abstraction that we looked at in the last lesson. By making generic methods abstract in
the superclass we can be assured that the overridden method implementations are in place in the subclass at runtime. If you remember we have to implement all abstract methods from the
superclass in the first concrete subclass. We will use an array to illustrate polymorphic method invocation:
package com.server2client;
/*
Polymorphic Array
*/
public class PolymorphicArray {
public static void main (String[] args) {
Truck[] truck = new Truck[2]; // Array declaration
truck[0] = new HGV();
truck[1] = new Garbage();
for (int i=0; i < truck.length; i++) {
truck[i].load("stuff");
}
}
}
Save, compile and run the file in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our PolymorphicArray
class. The JVM
uses virtual method invocation to dynamically invoke the correct subtype override. The
overrides in the HGV
and Garbage
classes are shown below:
package com.server2client;
public class HGV extends Truck {
...
public void load(String payload) {
System.out.println("We are transporting " + payload + " in our Heavy Goods Vehicle." );
}
}
package com.server2client;
public class Garbage extends Truck {
...
public void load(String payload) {
System.out.println("Our garbage truck eats " + payload);
}
}
We can happily include new subtypes in the knowledge that any method overrides will be called correctly on that subtype.
Polymorphic Arguments Top
We can also make the arguments to our methods polymorphic. We will write a new class and a test to see this in action:
package com.server2client;
/*
A Transit Class
*/
public class Transit {
private int capacity = 4;
public void hold(Vehicle v) {
v.carry(capacity);
}
}
Save and compile the file in directory c:\_OOConcepts in the usual way.
Here's our test class .
package com.server2client;
/*
Test class for polymorphic arguments
*/
public class TestPolyArgs {
public static void main (String[] args) {
Vehicle bus = new Bus();
Vehicle hgv = new HGV();
Transit transit = new Transit();
transit.hold(bus);
transit.hold(hgv);
}
}
The above screenshot shows the output of running our TestPolyArgs
test class. As you can see we are passing the Vehicle
supertype as an argumment to the hold()
method. The
JVM
uses virtual method invocation to dynamically invoke the correct Carry()
method based on the actual object type at runtime.
Polymorphic Return Types Top
Lastly we will look at using polymorphic return types. We will write a new test class to see this in action:
package com.server2client;
/*
Test class for polymorphic return
*/
public class PolymorphicReturn {
public static void main (String[] args) {
// Array declaration below is creating an Array object so can use Abstract supertype
Vehicle[] vehicle = new Vehicle[4];
for (int i=0; i < 4; i++) {
vehicle[i] = createVehicle(i);
vehicle[i].carry(i);
}
}
/*
Create a Vehicle subtype and return the Vehicle supertype
*/
static Vehicle createVehicle (int i) {
Vehicle vehicle;
switch (i) {
case 0:
vehicle = new Car();
break;
case 1:
vehicle = new HGV();
break;
case 2:
vehicle = new Bus();
break;
default:
vehicle = new Garbage();
}
return vehicle;
}
}
The above screenshot shows the output of running our slightly contrived PolymorphicReturn
test class. We are returning the Vehicle
supertype from the createVehicle()
method. The
JVM
uses virtual method invocation to dynamically invoke the correct Carry()
method based on the actual object type on return.
We will return to polymorphism when we look at polymorphism and interfaces in the Deeper Into Polymorphism lesson later in the section.
Static Overrides? Top
I know what you're thinking, what on earth is something about statics doing in a lesson on polymorphism. Well, now we have talked about how polymorphism works I just want to hammer home the point that it only relates to overridden instance methods and nothing else. I also wanted to talk about method hiding which relates to static methods in a subclass with the same identifier in the superclass. To look at this we first had to get our heads around the idea of virtual method invocation and how this works with supertypes and subtypes. Now that is done we can use the same principle to look at method hiding:
package com.server2client;
/*
Superclass to illustrate method hiding
*/
public class M {
public static void aClassMethod() {
System.out.println("The class method in M.");
}
public void anInstanceMethod() {
System.out.println("The instance method in M.");
}
}
package com.server2client;
/*
Subclass to illustrate method hiding
*/
public class N extends M {
public static void aClassMethod() {
System.out.println("The class method in N.");
}
public void anInstanceMethod() {
System.out.println("The instance method in N.");
}
}
package com.server2client;
/*
Test Class to illustrate method hiding
*/
public class TestHiding {
public static void main(String[] args) {
N n = new N();
M m = n;
M.aClassMethod();
m.anInstanceMethod();
}
}
In the above code the aClassMethod()
static method in class N
hides the aClassMethod()
static method in class M
. The anInstanceMethod()
instance method in
class N
overrides the anInstanceMethod()
instance method in class M
.
The above screenshot shows the output of running our TestHiding
test class after compiling classes M
and N
. As you can see the aClassMethod()
static method from the
superclass gets invoked via the class name. The overridden anInstanceMethod()
instance method from the subclass gets invoked via the object type.
Related Quiz
OO Concepts Quiz 9 - Polymorphism
Lesson 9 Complete
We continued our investigation of OO concepts by learning about polymorphism.
What's Next?
The next lesson looks at interfaces and the contracts we provide when using them.