Polymorphism is a big word that you can break down into “poly” which means many and “morphism” which means form. So, it just means many forms. In Java it means that the method that gets called at run-time (when the code is run) depends on the type of the object at run-time.
This is simliar to a toddler toy that has pictures of animals and when a handle is pulled an arrow spins. When the arrow stops the toy plays the sound associated with that animal.
If you were simulating this toy in software you could create an Animal class that had a makeNoise method. Each subclass of Animal would override the makeNoise method to make the correct noise for that type. This type of polymorphism is called inheritance-based polymorphism. You have a common parent class, but the behavior is specified in the child class.
For our lab, we can write the following lines the Store class without an error:
CISItem somePhone = new Phone("black iphone 12", ...); CISItem someBook = new Book("cool alrogrithms book", ...); CISItem someMagazine = new Magazine("old Wired zine", ...);
Notice that even though there are different types declared with new they are all extend CISItem, so this is valid.
In all of these cases, there are no errors at compile-time because the compiler checks that the “subclass is-a superclass” relationship is true. But at run-time, the Java interpreter will use the object’s actual subclass type and call the subclass methods for any overriden methods. This is why they are polymorphic – the same code can have different results depending on the object’s actual type at run-time.
Our CISItem class is then considered Polymorphic, because it can take many shapes or forms.
This is super useful when you want to build the Store class. You can now have an
ArrayList<CISItem> lagosItems = new ArrayList<>();
You can now add phones, books, magazines and Arduino’s without having to create four different ArrayLists.
We can also add parent methods to CISItems and because of inheritance we could end up with something like this:
ArrayList<CISItem> lagosItems = new ArrayList<>();
for(CISItem item : lagosItems)
{
item.printInfo();
}
We are guaranteed that any CISItem, regardless of whether it’s a Phone, Book, Arduino or Magazine, will have an implementation for the printInfo() method.
The variable nameList
declared below has a declared type of List
and an actual or run-time type of ArrayList
. The complier will check if the declared type has the methods or inherits the methods being used in the code and give an error if it doesn’t find the method(s). The List interface does have an add
method so this code will compile. At run-time the execution environment will first look for the add
method in the ArrayList
class since that is the actual or run-time type. If it doesn’t find it there it will look in the parent class and keep looking up the inheritance tree until it finds the method. It may go up all the way to the Object class. The method will be found, since otherwise the code would not have compiled.
In Java an object variable has both a declared (compile-time) type and an actual (run-time) type. The declared (compile-time) type of a variable is the type that is used in the declaration. The actual (run-time) type is the class that actually creates the object using new.
List<String> nameList = new ArrayList<String>(); nameList.add("Hi");
The variable message
declared below has a declared type of Object
and an actual or run-time type of String
. Since the declared type of message
is Object
the code message.indexOf("h");
will cause a compiler error since the Object
class does not have an indexOf
method.
Object message = new String("hi"); message.indexOf("h"); // ERROR!! Objects don't have indexOf!
At compile time, the compiler uses the declared type to check that the methods you are trying to use are available to an object of that type. The code won’t compile if the methods don’t exist in that class or some parent class of that class. At run-time, the actual method that is called depends on the actual type of the object. Remember that an object keeps a reference to the class that created it (an object of the class called Class
). When a method is called at run-time the first place that is checked for that method is the class that created the object. If the method is found there it will be executed. If not, the parent of that class will be checked and so on until the method is found.
For our CISItem example, let’s assume that you’ve implemented an Override of printInfo() in Book. what would happen if we do the following:
CISItem item = new Book("some name"...); item.printInfo();
Will the overridden print info be called or will the parent printInfo be called? What if printInfo in Book does this:
public void printInfo() { toString(); }
Which toString will be called? The parent or the child? Try it out!