Chapter 5 - Java for Beginners Course

Inheritance - Methods and Fields

In the last 2 sections we covered how to define inheritance relationships between classes and also how inheritance relates to the constructors of a class and the order of construction of objects.

In this section we’ll cover how inheritance affects fields and methods between a subclass and its superclass(es). In specific, we’ll cover the super keyword, method overriding and field hiding.

For this section assume we’re building a system for a Toy store and we’ll use the following two classes to introduce the concepts:

public class Toy {
    private String name;

    int fromAge;

    public Toy(String name, int fromAge) {
        this.name = name;
        this.fromAge = fromAge;
    }

    public void printDescription() {
        System.out.println("Toy: " + name + " can be used from age: " + fromAge);
    }

    public void printFromAgeDetails() {
        System.out.println("The minimum age for this toy is: " + fromAge + " years");
    }
}

The above Toy class will be used as our superclass and contains the name of the toy and a fromAge that indicates the minimum age the toy can be used from.

The Toy class above exposes the fromAge field with default access instead of being a private field. This is not recommended and only done to illustrate some concepts in this section.

We’ll now define a subclass RemoteControlCar of our Toy class:

public class RemoteControlCar extends Toy {

    int fromAge;

    public RemoteControlCar() {
        super("Remote Control Car", 10);
        fromAge = 10;
    }

    @Override
    public void printDescription() {
        System.out.println("*** RemoteControlCar class ***");
        System.out.println("This is a remote control car and is suitable from " + fromAge + " years");
    }

}

Method Overriding

In the example classes above, you’ll note that we provided a printDescription() method in both classes with the same signature. When this occurs, we’re telling Java that we want our subclass method to override the original method provided in the superclass.

By overriding the method in our subclass, Java will invoke the overriden method instead of the original one in the superclass.

Let’s give this a try:

In the example code run the JavaInheritanceAndMethodsApp
Toy toyBoat = new Toy("Boat", 1);
toyBoat.printDescription();

System.out.println("--------------------------");

RemoteControlCar toyCar = new RemoteControlCar();
toyCar.printDescription();

Output:

*** Toy class ***
Toy: Boat can be used from age: 1
--------------------------
*** RemoteControlCar class ***
This is a remote control car and is suitable from 10 years

Method Overriding - Example explained

In this case we are creating 2 different objects, toyBoat of type Toy and toyCar of type RemoteControlCar.

When we invoke toyBoat.printDescription() Java uses the printDescription method defined in the Toy class. In the hierarchy of the Toy class (ToyObject) there is no other printDescription() method defined.

In this case, we get the following 2 lines in our output:

*** Toy class ***
Toy: Boat can be used from age: 1

Now, when we call toyCar.printDescription(), the toyCar hierarchy RemoteControlCarToyObject has 2 valid methods that match the signature printDescription(): the original method in the Toy class and an overridden version in RemoteControlCar.

In this case, Java will invoke the first overridden version of the method it finds in the hierarchy, in this case, the one in the RemoteControlCar class, hence why we get:

*** RemoteControlCar class ***
This is a remote control car and is suitable from 10 years

in the output.

Try removing the printDescription() method in the RemoteControlCar class and run the program again. What result do you get?

Invoking the original method in the superclass

From a subclass, we can invoke the original method provided in a superclass. This can be achieved by using the super keyword in Java that allows us to access methods (overridden or not) and fields from a subclass.

To illustrate this, let’s assume we have a new class in our hierarchy called WifiRemoteControlCar that extends our RemoteControlCar class as follows:

public class WifiRemoteControlCar extends RemoteControlCar {

    @Override
    public void printDescription() {
        System.out.println("This RemoteControlCar can be controlled via WiFi");
        super.printDescription();
    }

}

And let’s create an instance and invoke that method:

WifiRemoteControlCar wifiCar = new WifiRemoteControlCar();
wifiCar.printDescription();

Output:

This RemoteControlCar can be controlled via WiFi
*** RemoteControlCar class ***
This is a remote control car and is suitable from 10 years

In this example, when we invoke wifiCar.printDescription() we already know that Java will invoke the overridden method defined in our new class.

Inside that method, we added a call to super.printDescription() and in this case Java will invoke the first printDescription() method in the class' hierarchy starting from the superclass.

For the WifiRemoteControlCar, the hierarchy is WifiRemoteControlCarRemoteControlCarToyObject, and the first printDescription() method Java finds starting from the superclass is in RemoteControlCar.

Hence we get the output of our RemoteControlCar method as well in the output.

Accessing fields from the superclass

As mentioned above, the super class also provides us access to the fields of the superclass. Let’s add the following method to our WifiRemoteControlCar class:

public class WifiRemoteControlCar extends RemoteControlCar {
    //...

    public void printWifiCarFromAge() {
        int minAge = super.fromAge;
        System.out.println("This wifi car is suitable from age: " + minAge);
    }

}

In this case, we are accessing the fromAge field defined in our RemoteControlCar class using the super.fromAge statement, and we get the following result if we invoke it:

WifiRemoteControlCar wifiCar = new WifiRemoteControlCar();
wifiCar.printWifiCarFromAge();

Output:

This wifi car is suitable from age: 10

Is super required?

In the example above we used super.fromAge to tell Java that we want to access the fromAge field defined from a superclass. However, in the WifiRemoteControlCar class there is no other fromAge variable or field defined anywhere.

As there is no chance for ambiguity, Java can safely infer that the fromAge field comes from the superclass (as it is the only one available). In cases like this, there is no need to use the super keyword, and in this case super.fromAge and fromAge refer to the same field.

The same principle applies to method invocations, as long as there is no ambiguity the super keyword is optional.

Field hiding

The last topic to cover in this section is field hiding. You’ll have noticed that in both the RemoteControlCar class and the Toy class we defined an int fromAge field.

When this occurs, the fromAge field from the Toy class is said to be hidden.

Field hiding is not recommended and is a code smell.

In practice, when we have field hiding, the instance of an object ends up with 2 states/fields with the same name. In our case, an instance of RemoteControlCar would have a value in the fromAge field that exists from the RemoteControlCar class and a different fromAge value that exists from the Toy class.

Let’s take a look at this with an example:

In the example code run the JavaFieldHidingApp
RemoteControlCar car = new RemoteControlCar();

// we can change the fromAge value defined in the RemoteControlCar class
car.fromAge = 15;
// and as expected the fromAge in the message below should be 15
System.out.println("The min age for the RemoteControlCar is: " + car.fromAge);

// however, we don't have access to the fromAge field of the Toy car (it is hidden),
// unless of course, we use a method that provides us access to its value, like the
// Toy.printFromAgeDetails() method we defined
car.printFromAgeDetails();

Output:

The min age for the RemoteControlCar is: 15
The minimum age for this toy is: 10 years

In the example above, you can see we have 2 different values as we have 2 separate fromAge fields, one in each class. As mentioned before, field hiding is not recommended as it becomes confusing and the code becomes more difficult to read.

The example above is to illustrate the concept only.
Try removing the fromAge field in the RemoteControlCar class and run the program again. What result do you get?

A note on @Override

If you’re asking yourself about what the @Override is on top of our overridden methods, we’ll cover them in a later chapter, but for now:

  • When you see @ symbols, these are called annotations in Java. So, @Override can be read as the Override annotation.

  • Annotations are a form of metadata we provide in our code.

  • The @Override annotation in particular on top of a method tells the Java compiler that this is an overridden method.

Code in GitHub

Get the code for this tutorial using the links below.

Project Repo
Download code for this step
Main class for this step
Dependencies

This is a list of recommended tutorials or courses that might be useful before starting this one.

Contents
Welcome to the Course!
Course Introduction
Chapter 1 - Building Blocks
Quick introduction to Java Variables Classes And Objects Class Example - Defining a class Object Examples - Creating instances Java Application Example - Running our first app Accessing class members - The dot operator Packages - Organizing the code
Chapter 2 - Primitives and Operators
Primitives Arithmetic Operators Assignment Operator Unary Operators Equality and Relational Operators Conditional Operators
Chapter 3 - Statements and Control Flow
Expressions Statements If-Then Statement If-Then-Else Statement More If Statements Switch Statement While and Do-While Statements For Statement Branching Statements Exception Handling
Chapter 4 - Code Example
Example Project - A Simple Vending Machine Adding money Delivering Items Giving Change
Chapter 5 - Classes and Interfaces
Introduction Access Level Modifiers Class Declaration - Class, Methods and Fields Class Declaration - Constructors Inheritance Basics Inheritance - Constructors Inheritance - Methods and Fields Polymorphism Abstract Classes and Methods Interfaces Static Class Members Class Composition Final Classes and Class Members Generic Classes
Chapter 6 - Base Object Behaviors
Introduction Type Comparison Type Casting Object Equality - The Contract Object Equality - Common Pitfalls Object String Representation Garbage Collection Object Comparison Primitive Wrappers and Autoboxing
Chapter 7 - Data Structures
Introduction Arrays - Declaration and Creation Arrays - Basic Operations Core Collection Interfaces List and ArrayList - Basic Operations ArrayList Internals Introduction to Hash Tables Map and HashMap - Basic Operations Set and HashSet - Basic Operations
Chapter 8 - Anonymous classes and lambdas
Introduction Filtering a List Anonymous Classes Lambdas Built-in Functional Interfaces
Chapter 9 - Streams
Introduction Creating Streams Intermediate Operations Terminal Operations