Inheritance

We have discussed briefly about inheritance earlier. Today we will understand the concept in detail.

A story

Let’s assume you are working as a developer in a mobile company called YouPhone. Your manager asked you to write code for the first model MegaX1

MegaX1 would be capable of –

  1. Making a call.
  2. Receiving call.
  3. Sending a text message.

Also, individual MegaX1 mobile phones should have a unique IMEI number.

You have written a lot of similar codes. So, you have started right away and created a MegaX1 class as below.

public class MegaX1 {
    private final String imeiCode;
    
    public MegaX1(String imeiCode) {
        this.imeiCode = imeiCode;
    }
    
    public void makeCall() {
        System.out.println("Making a call");
    }
    
    public void receiveCall() {
        System.out.println("Receiving a call");
    }
    
    public void sendMessage() {
        System.out.println("Sending text message");
    }

    public String getImeiCode() {
        return imeiCode;
    }
}

Your manager is now impressed with you. Few months later, he decided to launch a new model MegaX2. As you are the best developer, he asked you to write the code. But he planned to include a new feature – record the conversation automatically if you receive a call.

You thought most of the code was written already. So you will just copy the code and write a new method to record and just call that method from the receiveCall() method.

public class MegaX2 {
    private final String imeiCode;
    
    public MegaX2(String imeiCode) {
        this.imeiCode = imeiCode;
    }
    
    public void makeCall() {
        System.out.println("Making a call");
    }
    
    public void receiveCall() {
        System.out.println("Receiving a call");
        recordCall();
    }
    
    public void sendMessage() {
        System.out.println("Sending text message");
    }
    
    public void recordCall() {
        System.out.println("Recording the conversation");
    }

    public String getImeiCode() {
        return imeiCode;
    }
}

To increase the revenue, your manager decided to launch one last model this year: MegaX3. He decided not to use the call recording feature but include a new cool feature to capture a photo. And guess what, he requested you to write the code as you have delivered so well in the past.

You thought, not a problem. As the record feature is not required, I’ll copy the code from MegaX1 class and add a new method to capture photos.

public class MegaX3 {
    private final String imeiCode;
    
    public MegaX3(String imeiCode) {
        this.imeiCode = imeiCode;
    }
    
    public void makeCall() {
        System.out.println("Making a call");
    }
    
    public void receiveCall() {
        System.out.println("Receiving a call");
    }
    
    public void sendMessage() {
        System.out.println("Sending text message");
    }
    
    public void takePhoto() {
        System.out.println("Capturing photo");
    }

    public String getImeiCode() {
        return imeiCode;
    }
}

After delivering the code, you were on holiday for Christmas and new year. But your manager called you and said – there is a problem with the send message feature. Sometimes the message is not getting sent. Please look into it urgently.

You were not very happy with the request. But you had no option other than doing what your manager has asked. After inspecting the code, you found there is one minor problem in the code and you have fixed that in all three classes.

The testing team then tested all three models and confirmed everything is working fine. You thought the fix didn’t take much time and there is no other issue in the code.

After a few weeks, once you are back in the office, your manager said, he is planning to introduce a new feature to all models to compete with other brands. All the mobiles should be capable of playing songs.

So, you have added a new method playSong() to all three classes. Again the testing team tested all the classes, but reported one issue. So you have fixed that in every class and requested the testing team to test one more time.

The problem

As you are a smart developer, you realized that there are some serious problems.

  • If there is any issue, you have to apply the fix in all three classes. This is because you have duplicate codes across classes.
  • If some new feature is required, you have to add the feature individually to all three classes. This is wasting a lot of time.
  • This is increasing the development time and testing time as every time tester needs to test all three models.
  • The demand for new features is never ending and there is always some scenario that you have not thought of. So, while testing, testers are identifying new issues in existing code.
The solution

So, you have looked at all three classes that you have written.

You have realized –

  • The instance variable imeiCode is common for all three classes.
  • The methods makeCall(), receiveCall() and sendMessage() are almost identical. Only difference is, in MegaX2 class, along with receiveCall() you have to call the recordCall() method.
  • MegaX2 class has one extra method recordCall() and MegaX3 class has one extra method takePhoto().

Then you thought, wouldn’t it be nice if you put all the common instance variables and methods in a common class and link the individual classes to that common class. The distinct behavior will be there inside individual classes. If you need to upgrade a method, like a record call during receiveCall() method in MegaX2 class, you will just modify the method in that particular class.

This is exactly what java inheritance will help with.

Inheritance

Inheritance is a mechanism in which one child object acquires all the properties and behaviors of another parent object. The benefit is, you can create new classes on top of existing classes and can reuse methods and fields of the parent class. If required, you can add new methods and fields in your child classes.

So in our example, we can create a parent class Mobile and put the common fields and methods there. All three classes will inherit those fields and methods from the Mobile class and add the unique methods inside respective child classes.

As all the child classes is a type of parent class (i.e. Mobile), we can say inheritance represents the IS-A relationship. For example, MegaX1 is a Mobile or MegaX3 is a mobile.

The benefit you are getting is –

  1. You avoid duplicate code. Just write the common behaviors in parent class and reuse those behaviors in child class. If there is any issue, just fix that issue in a single place in the parent class and test that particular class only. No need to change anything in child class.
  2. Also, if you need to add a new method for all the child classes, just add that method in the parent class. All the child classes will inherit that method automatically.
The redesign

First create a parent Mobile class.

public class Mobile {
    private final String imeiCode;
    
    public Mobile(String imeiCode) {
        this.imeiCode = imeiCode;
    }
    
    public void makeCall() {
        System.out.println("Making a call");
    }
    
    public void receiveCall() {
        System.out.println("Receiving a call");
    }
    
    public void sendMessage() {
        System.out.println("Sending text message");
    }

    public String getImeiCode() {
        return imeiCode;
    }
}

Create three child classes MegaX1, MegaX2 and MegaX3. Those child classes will extend the parent Mobile class. As MegaX2 class needs an upgraded receiveCall() method, this class will override the parent class method.

public class MegaX1 extends Mobile {
    public MegaX1(String imeiCode) {
        super(imeiCode);
    }
}
public class MegaX2 extends Mobile {
    public MegaX2(String imeiCode) {
        super(imeiCode);
    }
    
    public void receiveCall() {
        super.receiveCall();
        recordCall();
    }
    
    public void recordCall() {
        System.out.println("Recording the conversation");
    }
}
public class MegaX3 extends Mobile {
    public MegaX3(String imeiCode) {
        super(imeiCode);
    }
    
    public void takePhoto() {
        System.out.println("Capturing photo");
    }
}

In case of inheritance, a child object can be referred to by parent class reference. So we can do both –

  • Mobile x1 = new MegaX1("IMEI X1");
  • MegaX1 x1 = new MegaX1("IMEI X1");

If the method exists in the parent class, we can call the method by parent class reference. But if the method exists only in child class, we have to use child class reference. Because the parent class is not aware of such a method.

Now, let’s test the application –

Mobile x1 = new MegaX1("IMEI X1");
Mobile x2 = new MegaX2("IMEI X2");
MegaX3 x3 = new MegaX3("IMEI X3");
		
x1.receiveCall();
x2.receiveCall();
x3.takePhoto();

Output:

Receiving a call 
Receiving a call
Recording the conversation
Capturing photo
Method resolution with inheritance

Consider the code below –

Mobile x2 = new MegaX2("IMEI X2");
x2.receiveCall();

When we have executed the code, the output was –

Receiving a call
Recording the conversation

You may ask, as the method receiveCall() is present in both Mobile class and MegaX2, why is the method present in MegaX2 is executed, not the method present in Mobile class?

The answer is, JVM always calls the most specific version based on object type. In the example above, the reference type is Mobile, but the actual object type is MegaX2. So the method present in MegaX2 is called.

If a method doesn’t exist in the actual object type, JVM will look at the immediate parent class and so on until JVM finds the method. Once the method is found, it will be executed. Please look at the below example to understand it clearly.

Rules for overriding

We have discussed overriding and overloading earlier. If a subclass (child class) has a method with the same signature as the parent class, it is known as method overriding. Normally we override a method to upgrade or implement the functionality. In our inheritance example, MegaX2 class has overridden the method receiveCall() present in Mobile class to include the record feature. Though it is not mandatory, it is recommended that while you are overriding, mark the child method with @Override annotation. If you don’t use that annotation and don’t follow the rule of overriding, the compiler will consider the child class method as a separate method. But if you use that annotation, you request the compiler to verify if you are actually overriding following the rule.

To test this, create a parent class –

public class Parent {
    protected int sum(int a, int b) {
        return a + b;
    }
}

Without @Override annotation below code works just fine as the compiler will treat the child class method as a separate method.

public class child extends Parent {
    protected int sum(int b) {
        return super.sum(0, b);    }
}

But if you use the annotation, it will fail during compilation  stating: The method sum(int) of type child must override or implement a supertype method.

public class child extends Parent {
    @Override
    protected int sum(int b) {
        return super.sum(0, b);    }
}

You should keep in mind couple of rules while overriding –

  1. Arguments must be the same. We have already tested this in the above example.
  2. Return type must be compatible. For example, if the return type in the parent class method is Mobile, you can change that to MegaX1. But the reverse is not true.
  3. The method cannot be less accessible. For example, if the access modifier is protected in parent class, you are allowed to change it to public, but not to private.
Prevent overriding

There are multiple ways to prevent overriding –

  1. Make the method final: If the method is final, overriding is not allowed.
  2. Make the method private: If the method is private, that method is not visible to the child class. So there is no question of overriding.
Prevent inheritance

There are couple of ways –

  1. Make the class final: If a class is final, you are not allowed to extend it.
  2. Make the constructor private: Remember constructor chaining – every child class constructor makes a superclass constructor call. If the superclass constructor is private, it is not accessible from child class and as a result, inheritance won’t be possible.
Can a class inherit multiple parent classes?

No. Multiple Inheritance Is Not Supported Java. To understand why, create two parent classes – Parent1 and Parent2.

public class Parent1 {
    public void getColor() {
        System.out.println("Red");
    }
}
public class Parent2 {
    public void getColor() {
        System.out.println("Blue");
    }
}

Now if a child class extends both Parent1 and Parent2 and call getColor() method, which method will be called, will it be of Parent1 or Parent2. This is clearly causing ambiguity problem. Because of which multiple inheritance is not supported in java.

That’s it for now. Hope the concept of inheritance is clear now. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.