Java Tutorial 5: Inheritance and Polymorphism

Inheritance and polymorphism: two big words to strike fear into the heart of any new Java programmer. However, the concepts that they refer to are not that complex.

Inheritance in Java



Let's take a look first at inheritance. Inheritance allows you to create child classes of existing classes. Why would you want to do such a thing? Well let's say you've got a class called Robot. Robot contains nothing other than some basic methods needed by all Robots. For example:


public class Robot {
    public void start() {
        System.out.println("Robot started.");
    }
    
    public void work() {
        System.out.println("Robot working.");
    }
    
    public void stop() {
        System.out.println("Robot stopped.");
    }
}





As you can see, the Robot class merely defines start(), work() and stop() methods, and each method merely prints out what it's supposed to do.

We'll create a class with a main method to run Robot.


public class Application {


    public static void main(String[] args) {
        Robot robot = new Robot();
        
        robot.start();
        robot.work();
        robot.stop();

    }
}




Running this program produces the following output:

Robot started.
Robot working.
Robot stopped.




All very well. But what if we want to create particular types of Robots that "inherit" all the functionality of Robot, but also add new functionality?

For instance, let's create a WasteDisposalRobot:

public class WasteDisposalRobot extends Robot {
    public void findWaste() {
        System.out.println("Finding waste");
    }
}




Using the keyword extends, we've create a WasteDisposalRobot that can do everything that Robot does, and also adds a findWaste() method.

We can use it as follows:

WasteDisposalRobot robot = new WasteDisposalRobot();
        
robot.start();
robot.findWaste();
robot.work();
robot.stop();




Robot started.
Finding waste
Robot working.
Robot stopped.




What we have here is inheritance at work. The WasteDisposalRobot is a subclass or child class of the Robot class; the Robot class is its superclass or parent class.

WasteDispoalRobot can do everything that Robot can do, and we can add new methods (and data) to it also.

Method Overriding



What if we've created a new subclass of some superclass, but we don't like one or more of the methods in the superclass? We'd like to change it to do something else. We can do that simply by defining the method again in the child class.

In the following code, we've overridden the work() method in the Robot parent class so that it does something different in the WasteDisposalRobot child class.


public class WasteDisposalRobot extends Robot {
    public void findWaste() {
        System.out.println("Finding waste");
    }
    
    @Override
    public void work() {
        System.out.println("Disposing waste!");
    }
}




Robot started.
Finding waste
Disposing waste!
Robot stopped.




Note the @Override directive just before the overridden method. This is not obligatory, but you should always use it. It tells the Java compiler that you intend to override a method in the parent class. If you misspell the method name and try to override a method that does not exist, the compiler will warn you by throwing an error.

Instance Variable Inheritance



It might occur to you to wonder what happens with instance variables. If the Robot superclass has some instance variables, do the child classes also have access to these variables? This depends on whether you define the instance variables in the parent class using the public, private or protected access specifiers, or with none at all. We'll look at this in more detail later, but for now let's just say that as long as instance variables are not private, they can be accessed by subclasses.

Let's see an example. Here I've placed all relevant code in one file to make it easier to read.

class Fruit {
    String name;
    
    Fruit() {
        name = "Fruit";
    }
    
    public String getName() {
        return name;
    }
}

class Banana extends Fruit {
    Banana() {
        name = "Banana";
    }
}



public class Application {


    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Banana banana = new Banana();
        
        System.out.println(fruit.getName());
        System.out.println(banana.getName());
    }

}




The constructors of both classes set the name instance variable. The Banana class extends the Fruit class (i.e. inherits from it); its constructor also has access to the name instance variable, which it sets. Then when name is retrieved from either class using the getName() method, an appropriate name is returned and displayed.

Fruit
Banana




Constructor Inheritance



Constructors are inherited like other methods, and in fact when you construct a child object, the default constructor of its parent is called automatically first.

class Fruit {
    Fruit() {
        System.out.println("Fruit constructed");
    }

}

class Banana extends Fruit {
    Banana() {
        System.out.println("Banana constructed");
    }
}

public class Application {

    public static void main(String[] args) {
        Banana banana = new Banana();
    }
}





Fruit constructed
Banana constructed




If there is no default constructor in the parent class, you must define a constructor explicitly in the child class. If you want, you can then call the appropriate constructor in the parent class using the super keyword.

class Fruit {
    Fruit(String name) {
        System.out.println("Fruit constructed with name: " + name);
    }

}

class Banana extends Fruit {
    Banana() {
        super("Banana");
    }
}

public class Application {

    public static void main(String[] args) {
        Banana banana = new Banana();
    }
}




Fruit constructed with name: Banana




Polymorphism in Java



Polymorphism: big word, simple concept. Its literal meaning is "many shapes". But that tells you nothing. Polymorphism just means that, basically, once you've got a child class, you can use objects of that child class wherever you'd use objects of the parent class. Java will automatically invoke the right methods.

For instance, even if we have a variable with the type of a parent class, we can assign it to a child class and we can call overridden methods in the child class using that variable.

Let's see an example.

class Fruit {
    
    public void show() {
        System.out.println("Fruit");
    }
}

class Banana extends Fruit {
    
    @Override
    public void show() {
        System.out.println("Banana");
    }
    
    public void makeBananaTree() {
        System.out.println("Making a tree");
    }
}

public class Application {

    public static void main(String[] args) {
        Fruit banana = new Banana();
        
        banana.show();
        
        // The following WILL NOT work;
        // Variables of type Fruit know only
        // about Fruit methods.
        // banana.makeBananaTree();
    }
}





Banana





Of course, you can't assign a Banana to a Fruit variable and then use it to call methods that belong only to Banana and not to Fruit. Fruit only knows about Fruit methods. A variable of the type of a particular class knows only about methods defined in that particular class and its superclasses. It doesn't know about methods defined in subclasses, even though you can assign objects of subclass types to the variable (Banana objects to Fruit variables, as in this example) and the overridden methods will be correctly called.