Inheritance is a type of object-oriented design that is used to create a new class using attributes of an existing class. In these instances, we refer to the existing class as the base class and the new class as the derived class. Inheritance is valuable for instances where we have many similar objects that share properties, but also have properties unique to the object itself.
Suppose you were designing a system to track information about employees at a company. At this company, you have hourly employees, salaried employees, managers, and executives. When we think about the attributes of each of these employees, we realize that they have a lot in common. Every employee, regardless of their employment type, will have a name, age, and employee ID, for instance. If we apply inheritance, we can implement a lot of these properties without needing to repeat code for every object. To do this, we start by defining a base class that each class will derive from. I will call this class Employee, and it will store properties for name, age, and employee ID.
The base class lays out the methods and properties that will be used by the derived classes. In this case, all the employees have a name, age, and employee ID, so we implement those properties and provide ways to access them. When we have a property that is unique to a specific type of employee, we just extend the base class and implement the single different property or method to use. For example, suppose hourly workers were paid $18 per hour for 25 hours of work, and salaried workers were paid $25 per hour for 40 hours of work. We could create two derived classes, SalariedWorker and HourlyWorker, each with a different pay rate and hours of work. To do this, we create a class that extends the Employee class.
When we add extends Employee to the class definition, it tells Java that we wish to derive our class from the Employee base class. This means that we can use any methods that are defined in Employee, as well as write our own methods and properties. In this case, we are defining new properties for the hours worked, and the pay rate of our salaried employees. We also added a new method called getPayForWeek, which returns the hours worked multiplied by the pay rate.
In the constructor of SalariedWorker, you will see that we use the keyword super(). This means call the constructor of the base class, using the parameters provided. This allows us to set the properties given in the base class without having to rewrite all the logic of the base class. We could also add properties and additional logic if we choose to, the super call will simply set up the base class constructor for the properties defined in it.
When we use the SalariedWorker class, we define our object as if we were defining an Employee object. Once this is done, we can use any methods that exist in the Employee object, as well as any methods that exist in our SalariedWorker object.
In addition to being able to use properties and methods from our base class, we can also change the functionality of existing methods using overriding. As an example, suppose that the Employee class implements a toString method, which concatenates the properties of the object.
If we wanted SalariedWorker to add the pay related information to the String output, we can override the toString method to change the logic of it.
The @Override keyword is an annotation which informs the compiler that this method is meant to override the implementation in the base class. This ensures that when we call toString with a SalariedEmployee object, our overridden implementation is used in place of the base class implementation. Inside of the implementation of our toString class, we start by calling super.toString(), which gets the current toString from the base Employee class. From here, we concatenate our additional properties, and return the results.
Inheritance can be used in many situations like the employee example to reduce code repetition. The more we repeat code, the more likely it is to duplicate errors and duplicate the effort required to fix problems. By avoiding code repetition, we reduce the complexity of our programs, and in turn make easier and more maintainable products.