DEV Community

Cover image for Inheritance and Polymorphism - [OOP & Java #2]
Liu Yongliang
Liu Yongliang

Posted on

Inheritance and Polymorphism - [OOP & Java #2]

Recap

Talking about abstraction entails us to talk about the concept of a barrier. We abstract and encapsulate because of this barrier between two actors: a client and an implementer.

For a client to call an implementer, that implementer has to be created and reliable in the first place. It means the client code is dependent on the implementer code. Also, the implementer can hide stuff from the client through data/function abstraction as well as packaging/information hiding. I think this is unsurprising to us. We all serve as a client in our quotidian life. We use electronic gadgets that most of time are treated as a mental black box. We do not know what is going on behind those covers. But we trust that they are reliable and appreciate the fact that we just need to know enough to use them.

Inheritance

Inheritance is a well-known idea in Object-Oriented Programming. It focuses on the relationship between two objects where one of the object is-a another object. For example, A car is a vehicle. Hence, a car object class could inherit from a vehicle object class. This is not a requirement but more of a design principle. In essence, the OOP principles hope to achieve the following:

Where similar functions are carried out by distinct pieces of code, it is generally beneficial to combine them into one by abstracting out the varying parts.
- Benjamin C.Pierce

Let's go through an example.
Suppose you want to have a Paper object that describes a piece of paper's dimension and color.

class Paper{
    private int height;
    private int width;
    private String color;

    Paper(int height, int width, String color){
        this.height= height;
        this.width = width;
        this.color = color;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, if you need to have a standard, default printer paper (A typical A4 sized paper), there are three ways to do it.

  • 1. Construct a method that calls the Paper constructor with default parameters
Paper PrinterPaper(){
    return new Paper(297, 210, "white");
}
Enter fullscreen mode Exit fullscreen mode
  • 2. Overload the constructor of the Paper class. It means that by declaring another constructor with a slightly different method signature from the original constructor, Java treats it like a different function and Java will know which one to call based on the given parameter.
class Paper{
    private int height;
    private int width;
    private String color;

    // original
    Paper(int height, int width, String color){
        this.height= height;
        this.width = width;
        this.color = color;
    }
    // added, will be called if no parameters are given
    Paper(){
        this.height= 297;
        this.width = 210;
        this.color = "white"
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 3. The last way is through inheritance. Since a PrinterPaper is a Paper, we can extend the Paper class
class PrinterPaper extends Paper{
    PrinterPaper(){
        super(297,210,"white");
    }
}
Enter fullscreen mode Exit fullscreen mode

We can see that inheritance is merely one way of doing things. It usually works when there is a clear is-a relationship between Objects. Comparing the 3 ways of constructing PrinterPaper, the 3rd way seems to be relatively shorter in code size and also helps in the organization of code.

  • Stuff related to PrinterPaper can be isolated into the separate PrinterPaper class (separation of concerns).
  • Stuff that is shared between PrinterPaper and Paper can be used with the keyword "super" that invokes the parent's property or methods.

Polymorphism

Of many forms

In Java and especially with inheritance, we would like to avoid duplication of code for shared functionalities. However, there exists a need for code modification. That need is often inherent in the creation of a new class. If not, we won't be separating out anything meaningful in the first place.

In the case of inheritance, while we specify that there is code to be shared between the parent and the child, there will also be two major differences in the child class.

  • The child class needs to perform something that the parent class will never perform. In this case, we write new methods in the child class.
  • The child class needs to perform something that the parent class also performs, but with a slight twist. In this case, we use method overriding.

Note: If the child does nothing different from the parent class, it automatically inherits the same methods from the parent class.

There are two often overridden methods in Java.

  • toString(): affects how the object is printed to screen, modified to show some characteristics of the object when printed out.
  • equals(): checks if two objects are the same based on criteria on top of the fact that the objects are the exact copy. Some notes on the equals method:
// using the Paper example
@override
public boolean equals(Object obj){
    if(this == obj){
        return true; // if exact same copy
    } else if (obj instanceof Paper){ 
    // if belongs to the same class
        Paper p = (Paper) obj; // IMPT! type-cast before you can use the 
                            // Paper class methods
        return this.height == p.height && this.width == p.width 
&& this.color.equals(p.color);
    } else { // not even of the same class
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

The toString method is easy to understand. You simply format a string to be printed based on some property of your choosing. It does not need to be dynamic. It works as long as it returns a string.

@override
 public String toString() {
    return this.color + " paper of size: " + this.height + " " + this.width;
}
Enter fullscreen mode Exit fullscreen mode

A reminder is that the method signature must be exactly the same to override another method. It means if the method has the header of

public boolean equals(Object obj){
//...
}
Enter fullscreen mode Exit fullscreen mode

You have to use the same header. So, replacing "(Object obj)" with your own class object does not work.

// Example of a wrong way to override
@override
public boolean equals(Paper p){
//...
}
//This is wrong
Enter fullscreen mode Exit fullscreen mode

Usually, the return type is the same in order for one method to override another method. However, there is an acceptable case where the return type is more specific than the parent method (NOT the other way round!). This will work because by the principle of substitutability, a subclass has to meet the expectations of its parent class in all aspects. Therefore, the more specific object that is returned will still work with any client code that is working with the more general object (the parent). I will elaborate on this in the next article.


ending gif

Top comments (0)