Polymorphism
Problem
Pay with an if for each kind
The company has regular employees, managers with a bonus and part-time interns. Someone made one Employee class with a "role" field, and the pay is worked out like this:
static int monthlyPay(Employee e) { if (e.getRole().equals("manager")) { return e.getSalary() + e.getBonus(); } else if (e.getRole().equals("intern")) { return e.getSalary() / 2; } return e.getSalary();}It works. But then a new kind of employee comes along: a contractor. You need a new else if here, and in the method for taxes, and in the method for the payslip, and everywhere anyone asked about the role. And every employee carries a bonus field, even though only managers get a bonus.
The code for the manager is scattered all over the program instead of being in the manager.
The idea
Each class decides for itself
With inheritance from the last lesson, Manager and Intern are separate classes. Let each one say what its monthly pay is. The parent has a general version, and the child writes its own version of the same method:
class Employee { // ... name, salary, the constructor and the getters
public int monthlyPay() { return salary; }}
class Manager extends Employee { // ... bonus and the constructor
@Override public int monthlyPay() { return getSalary() + bonus; }}Employee ivan = new Employee("Ivan", 2000);Manager maria = new Manager("Maria", 3000, 500);System.out.println(ivan.monthlyPay());System.out.println(maria.monthlyPay());20003500This is called overriding a method. The child writes a method with the same name, the same parameters and the same return type. For objects of the child class, its version replaces the parent's.
@Override is an annotation: a note for the compiler. It says: "this method overrides a method of the parent, check it". It is not required, but always write it. In a moment you will see why.
More precisely, if you are curious
- In Java any method can be overridden, except
private,staticandfinalmethods.finalbefore a method says "child classes cannot change it". - The child cannot make the method more hidden: if it is
publicin the parent, it stayspublic.
Try it
What changed?
The intern overrides only the method for the monthly pay:
What will the program print?
class Intern extends Employee { public Intern(String name, int salary) { super(name, salary); }
@Override public int monthlyPay() { return getSalary() / 2; }}
Intern ana = new Intern("Ana", 1600);System.out.println(ana.monthlyPay());System.out.println(ana.getSalary());The intern's monthlyPay() returns half the salary. The salary itself does not change, and getSalary() is inherited from Employee as it is.
The idea
One array for all
A manager is an employee. So a variable of type Employee can point to an object of type Manager, and an Employee[] array can hold any kind of employee:
Employee[] staff = { new Employee("Ivan", 2000), new Manager("Maria", 3000, 500), new Intern("Ana", 1600)};for (Employee e : staff) { System.out.println(e.getName() + ": €" + e.monthlyPay());}Ivan: €2000Maria: €3500Ana: €800The loop calls the same method, e.monthlyPay(), and the result is different. The version that runs is the object's, not the one of the variable's type. This is called polymorphism: Greek for "many forms". One call, and each object answers in its own way.
Now a new kind of employee is a new class with its own monthlyPay(). The loop and the rest of the program stay as they are. There is no if on the role and no bonus field in employees who get no bonus.
The type of the variable, though, decides what you can call. Through e you cannot reach the bonus, even if the object is a manager: the compiler only knows that e is some Employee. How to reach things that only some objects have is in the lesson "What is it really?".
See it
Which method runs
Employee[] staff = { new Employee("Ivan", 2000), new Manager("Maria", 3000, 500), new Intern("Ana", 1600)};for (Employee e : staff) { System.out.println(e.getName() + ": €" + e.monthlyPay());}Step by step, in words
- The array is of type Employee, but the objects are of three classes. Each slot holds an arrow.
- e points to Ivan, a plain Employee: Employee's monthlyPay runs.
- e points to Maria, a Manager: her own method runs, salary plus bonus.
- e points to Ana, an Intern: the intern gets half the salary.
- The same line, three different methods. The object decides, not the type of the variable.
The array holds three arrows of type Employee. Each one points to an object of a different class. On each round e points to the next object, and the call goes to that object's method: first in Employee, then in Manager, then in Intern.
The choice is made while the program runs, not when it compiles. The compiler does not know what will be in the array. It only checks that every Employee has such a method.
Try it
Find the bug
The program is the same, but the intern gets the full salary:
Ivan: €2000Maria: €3500Ana: €1600Here is the Intern class. Which line has the bug?
Pick the line with the bug.
monthlypay, with a small "p". That is a new method, not an override of monthlyPay. So through Employee the parent's version is called. With @Override above it, the program would not compile: "method does not override or implement a method from a supertype". That is why you always write @Override.The idea
Add to the parent, do not rewrite it
Sometimes the child does not want to replace the method completely, only to add something to it. A manager is described like an employee, plus the bonus. You call the parent's version with super.toString():
@Overridepublic String toString() { return super.toString() + ", bonus €" + bonus;}System.out.println(new Employee("Ivan", 2000));System.out.println(new Manager("Maria", 3000, 500));Ivan (€2000)Maria (€3000), bonus €500If Employee changes how it describes itself, the manager follows on its own.
This is also the toString() from the "Classes and objects" module. There you were already overriding a method: the version in Object. That is why @Override was above it.
More precisely, if you are curious
- You can call
super.method()from any method of the child, not only from the overriding one.
- Overriding is not the overloading from the "Methods" module. With overloading the name is the same and the parameters are different, and the compiler chooses by the arguments. With overriding everything is the same, and the object chooses while the program runs.
Try it
What is missing?
The intern is described like an employee plus the word "intern". What goes in the blank?
class Intern extends Employee {
public Intern(String name, int salary) {
super(name, salary);
}
@Override
public String toString() {
return .toString() + ", intern";
}
}super.toString() gives "Ana (€1600)", and the child adds ", intern". Without super. the method would call itself, forever.
Exercise
Each in its own way
Solve the tasks in your editor. Each task has an array of the parent type and one loop.
1. Animal shelter. An Animal class with a name and a makeSound() method that prints "some sound". Dog and Cat override it. Put one of each of the three in an Animal[] array:
Guest: some soundRex: Woof!Mitzi: Meow, meow!2. The student introduces themselves. Student from the last lesson already has a grade. Override introduce() so that it says the grade too. Use super.introduce(); do not copy the line from Person.
I am Georgi, 45 years old.I am Elena, 19 years old.Average grade: 5.53. Party. A Performance class with a title and a start() method. DanceMove, BreakDanceMove (inherits from DanceMove, adds difficulty) and Song override it. A Party class holds a Performance[] array and has a startParty() method that starts everything in order and at the end says the party has started.
Dancing moonwalkDancing windmill difficulty 8 out of 10Singing 'Bohemian Rhapsody'Starting raffleThe party has started!More tasks
- Contractor. Add a
Contractorto the employees, with hours and a rate per hour. Their monthly pay is hours × rate. Work out the payroll for Ivan, Maria, Ana and Peter (60 hours at €25):
Payroll: €7800- Payslip. Give
Employeeapayslip()method that returns the text "name: €pay" with the monthly pay. Write it only inEmployee. Why does it work correctly for the manager and for the intern too?
Test