Logout

Home OOP Option Last Next

D.2.3

Define the term polymorphism.

 

Teaching Note:

Actions have the same name but different parameter lists and processes.


Sample Questions

----

Q1. Discuss the use of polymorphism that occurs in this suite of programs (in the exam paper).

-----

Q2. Explain why having a getWeight() method in both the Train and Wagon classes does not cause a compiler error, even though the Train class does not inherit from theRollingStock class.

----

Q3. By making reference to OOP features, outline why it is possible for the Xyz class to have two constructors.

----

JSR Notes:

OOP Super Power # 3: Polymorphism

That’s my secret Cap, I’m always angry." - Bruce Banner (who morphs into The Hulk when angry) to Captain America.


Basic Definition

(In true superhero fashion,) the simplest definition of polymorphism can be taken by appreciating the two Ancient Greek roots of its name, "poly", meaning many/multiple, and "morph", meaning form. Polymorphism is the OOP feature in which a method can take on multiple forms.


Beyond the Basics

With polymorphism, within one program, two or more methods can be spelled exactly the same way, yet work differently. This can happen from two different situations, overloading, and overriding. Overloading is implemented at compile time, and overriding happens at runtime.

Compile time vs. Runtime

Before going any further, it would be a good idea to be clear on compile time vs. runtime. Compile time is when the code is checked for errors and, in the case of Java, made into an intermediary .class file. For us, using IntelliJ IDEA to program, IntelliJ automatically compiles the program each time just before we run our program.

Meantime runtime is the time during which the code is actually being run by the computer, and the user is interacting with it. It is important to note that the state of various parts of the program can change each particular time the program is run.

Overloading Polymorphism (at compile time) - this is where, within the same class, there is more than one method with the same name. This is possible because each overloaded method takes a particular number and type of parameters, different from the other methods with the same name. Or using more technical words, each overloaded method's signature (which describes its header line) is different, because of a different parameter list.

Most commonly, overloading of methods is seen with the constructor methods, i.e. overloaded constructors.

Java Example

public class Student{
    String name = "not set yet";
    int grade = -999;

    public Student( ){ //default constructor
    }

    public Student(String name){ //overloaded constructor # 1
        this.name = name;
    }

    public Student(String name, int grade){ //overloaded constructor # 2
        this.name = name;
        this.grade = grade;
    }
}


public class MainClass{
    public static void main(String[] args){
        Student s = new Student( ); // this will call the default constructor
        Student s1 = new Student("Sally");  //this will call the one-parameter overloaded constructor
        Student s2 = new Student("Bobby", 10); //this will call the two-parameter overloaded constructor
    ....
    }
}

 

Overriding Polymorphism (at runtime) - this is where, at different places on an inheritance hierarchy, there are methods with the same name in different classes and they even have the same number and type of parameters. The compiler of the program will keep track of whichever class object the method is bound to when it is instantiated during that particular run-time.

This is useful because similar functionality which should execute slightly differently for slightly different classes can be called the same thing. And even better, if an array of similar-level sub-classes is made, one spelling of a method can be called through a loop of the array, with the particular overridden method for each element being executed the right way at runtime.

Java Example

public abstract class MarketItem{
    ...
    ...
    public abstract double calculateCheckoutCost();
}

public class FoodItem extends MarketItem{
    ...
    double stickerPrice = scanner.readLine();
    ...
    public double calculateCheckoutCost(){
        return stickerPrice * 1.05; //so 5% tax on food
    }
}

public class LuxuryItem extends MarketItem{
    ...
   double stickerPrice = scanner.nextDouble();
    ...
    public double calculateCheckoutCost(){
        return stickerPrice * 1.4; //so 40% tax on luxury items
    }
}

public class MainForMarket{
    public static void main(String[] args){
        MarketItem [] items = new MarketItem[2];
        items[0] = new FoodItem("Celery", 20);
        items[1] = new LuxuryItem("Caviar", 300);
		double totalBill = 0;
        for(int i = 0; i < items.length; i++){
            totalBill += items[i].calculateCheckoutCost(); 
            //When this program runs the correct overriden methods will each be executed.
            //Actually, in this example we know items[0] is a FoodItem and items[1] a Luxury
            //item at compile time, but look below for a runtime decision situation.
        }
       System.out.println("Your total bill is " + totalBill.)
    }
}

An Even Better, But More Involved Main:

public class MainForMarket{
    public static void main(String[] args){
        ArrayList<MarketItem> items = new ArrayList<MarketItem>();
        Scanner scanner = new Scanner(System.in);
        boolean moreItemsToCheckIn = true;
        while(moreItemsToCheckIn){
            System.out.printnl("What is the next item to check in?");
            String nextItem = scanner.nextLine();
            System.out.println("What is its price?");
            double price = scanner.nextBoolean();
            System.out.println("Is it a luxury item? true/false");
            boolean isLuxuryItem = scanner.nextBoolean();
            if(isLuxuryItem){
                items.add(new LuxuryItem(nextItem, price));
            }
            else{
                items.add(new FoodItem(nextItem, price));
            }
            System.out.println("Do you have more items to check in? true/false");
            moreItemsToCheckIn = scanner.nextBoolean();
        }
		double totalBill = 0;
        for(int i = 0; i < items.size(); i++){
            totalBill += items[i].calculateCheckoutCost(); 
            //Shazam! Here is where at runtime the correct overriden methods will be chosen.
            //In this case, what itmes[1] and items[2] are will not even be known until runtime.
            //Polymorphism at its coolest!
        }
       System.out.println("Your total bill is " + totalBill.)
    }
}

In the example above, each calculateCheckoutCost() is overriding, in its own particular way the calculateCheckoutCost() method in the hierarchical level just above it.

Overriding toString()

The most common example of overriding that you will see in IB CS (and on IB CS exams) is the overriding of toString( ), which exists in the Object class, so all other classes can override it. You'll note that even though all classes in Java extend Object, "the mother of all classes", explicitly stating this in the first line of a program (i.e. class Xyz extends Object) is neither necessary nor expected.

It is good practice to always overload toString( ). It's generally expected that you will do so, in order to offer users a nicely formatted look to your object. If you don't override toString( ), by default toString( ) just prints out a string representation of the memory address where that object resides (like for a Car object, Car@fd128a95).

So here's a quick override of toString( ) for the FoodItem class used above:

public String toString(){
    String stringToOutput = "";
    stringToOutput += "***Food Mart - Number 1 for your Family!!!";
    stringToOutput += "* Food name: " + foodName;
    stringToOutput += "* Price:         " + price;
    stringToOutput += "***Remember, Your Family, Your Food Mart!!);
    return stringToOutput;
}

So System.out.println(foodItemXyz.toString()) would give an output like:

***Food Mart - Number 1 for your Family!!!
* Food name: Celery
* Price: 20
***Remember, Your Family, Your Food Mart!!

 

Full Polymorphism Example with both Overloading and Overriding

This has both two additional overloaded constructors, and toString( ) overridden.
(There possibly could be more hierarchical overriding implemented too, since the Teacher and TeachingAide classes both extend TeachingStaff, but this is not done here yet.)

public class SchoolClass{
    private ArrayList<Student> students = new ArrayList<Student>();
    private Teacher teacher = null;
    private TeacherAid teacherAid = null;
    private hasTeacherAide = false;

    public SchoolClass(){ //default constructor
    }

    public SchoolClass(ArrayList<Student> students, Teacher teacher ){ //overloaded constructor # 1
        this.students  = students;
        this.teacher = teacher;
    }

    public SchoolClass(ArrayList<Student> students, Teacher teacher, TeacherAide ta ){ //overloaded constructor # 2
        this.students  = students;
        this.teacher = teacher;
        this.hasTeacherAide = true;
        this.teacherAide = ta;
    }

    public ArrayList<Student> getStudents(){
        return students;
    }

    //other get methods

    public void setStudents(Student [] students){
        this.students = students;
    }

    //other set methods
    //other mutator methods

    public String toString(){ //overriding of toString() from Object
        return "Number of students: " + students.size() + " Teacher: " + teacher.getName();
    }
}
   
public class Student{ ... } public class Teacher extends TeachingStaff{ ... } public class TeacherAide extends TeachingStaff{ ... } public static void main(String[] args){ ...
}

 

In Summary, Back to the Assessment Statement:
Define the term polymorphism.

- Polymorphism is the OOP feature in which a method can take on multiple forms.

But just as likely you will get a question which asks "why/how is such-and-such possible", and it will be an example of polymorphism. So for an overriding situation, like with toString( ), your answer will go a little something like this:

- The runtime engine checks to see what class of object the method is bound to and so choose the correct one.

Or in terms of why/how is an overloaded constructor situation possible:

- The two constructors have different parameter lists, and so the compiler can differentiate between the two.
It will execute the constructor whose parameter set matches the arguments that were send. This overloading of constructors is an example of polymorphism.

 


 

+ Morelli textbook: pages: 133-136 and 350-353 for overriding and 355-356 for overloading

On-line textbook (open source) and additional resources.