Web/_posts/2019-10-31-java_6.md
Ell 08fc481589
All checks were successful
/ web (push) Successful in 1m1s
move website content to main directory
2024-06-14 17:49:53 +02:00

9.2 KiB

layout title description tags discuss archived
blog Java Tutorial, Part 6: Inheritance In this Java tutorial for beginners, we cover classes extending other classes and the instanceof keyword.
Programming
https://twitter.com/Ellpeck/status/1189904487722487809 true

For this tutorial, let's expand on the car dealership example from the end of the last tutorial: You have a car dealership, and you want to expand it to sell various other types of motor vehicles; let's say... motorbikes and trucks as well. Obviously, these different kinds of vehicles all have different properties: A motorbike requires the use of a helmet, and an important piece of information about a truck might be how big its storage area is.

Now, this is where object-oriented languages like Java shine.

Extending Other Classes

The concept of extending other classes is probably best explained with an example, so to start out with, let's create a class that represents a car with a certain amount of wheels:

public class Car {
    public int amountOfWheels;

    public Car(int amountOfWheels) {
        this.amountOfWheels = amountOfWheels;
    }
}

As you can see, this is just a simple class with a variable that can store how many wheels a vehicle has.

Now, let's imagine we want to create a class that represents a truck, and we would want any truck to store information on how many wheels it has, as well as how many cubic meters of storage it has available:

public class Truck {
    public int amountOfWheels;
    public int storageArea;

    public Truck(int amountOfWheels, int storageArea) {
        this.amountOfWheels = amountOfWheels;
        this.storageArea = storageArea;
    }
}

As you can see, just creating the class like usual isn't really that pretty, because now both our Car class and our Truck class have separate amountOfWheels fields, despite the fact that they really refer to the same thing: The amount of wheels of a vehicle.

What we'd like to be able to do instead is to have the amount of wheels stored in a different location that both our Truck and our Car class can access. The first step of achieving this is creating another class that only stores information both of our vehicles should have:

public class Vehicle {
    public int amountOfWheels;

    public Vehicle(int amountOfWheels) {
        this.amountOfWheels = amountOfWheels;
    }
}

Now, we can use the extends keyword that Java provides to cause both of our other classes to also have access to this information. Let's modify our Car class first and I'll explain afterwards what exactly our modification will do.

public class Car extends Vehicle {
    public Car(int amountOfWheels) {
        // Note that the line below will be explained in a bit
        super(amountOfWheels);
    }
}

As you can see, our Car class now extends Vehicle. This means that the Car class is basically just an upgraded version of the Vehicle class: It contains all of the information that the Vehicle class provides (including fields and methods), but it can also provide its own additional information. From the perspective of Car, Vehicle is the superclass, and from the perspective of Vehicle, Car is a child class.

It should be noted that any class can only extend one other class, so Car couldn't extend both Vehicle and, let's say, FueledDevice. However, the other way around is possible: Vehicle can be extended by multiple classes, meaning that we can modify our Truck class to also be a child class like this:

public class Truck extends Vehicle {
    public int storageArea;

    public Truck(int amountOfWheels, int storageArea) {
        super(amountOfWheels);
        this.storageArea = storageArea;
    }
}

As you can see, the Truck class now uses the amountOfWheels field from its superclass, but it defines its own storageArea field that only trucks have.

super

Now, you've probably been wondering what line 5 means: super(amountOfWheels). As we defined earlier, our Vehicle class takes one argument in its constructor: The amount of wheels that it has. As we also previously discussed, a constructor has to be called when a new object is created. So, when creating a Truck object, the Truck constructor is called, but, in turn, it needs to also implicitly call the Vehicle constructor.

When placing a super() call at the start of the constructor of a class, the constructor of the class that is being extended will be called. If that class takes a set of arguments in its constructor, we have to pass it those arguments, similarly to if we were creating an object of that class.

That's what our super(amountOfWheels) is doing here: It's calling the constructor of Vehicle with the amountOfWheels argument.

It should be noted that we don't necessarily have to pass a variable into the super constructor. For instance, we know for a fact that a truck will always have four wheels, so we can modify our Truck constructor to always specify 4 as the wheel amount given to the superclass:

public Truck(int storageArea) {
    super(4);
    this.storageArea = storageArea;
}

The Object class

It should be noted at this point that all classes extend the Object class implicitly (meaning you never have to write extends Object anywhere and it will still hold true). This will be useful pretty soon.

About Variable Types

Let's imagine we want to store the stock that we sell at our dealership, without specifying separate lists for the different kinds of vehicles we're selling. What we can do is have that variable be of type Vehicle, which will automatically allow instances of child classes to be stored in the variable (or list) like so:

ArrayList<Vehicle> stock = new ArrayList<>();
stock.add(new Car(4));
stock.add(new Truck(10));

// or in the case of variables
Vehicle car = new Car(4);
Vehicle truck = new Truck(10);

instanceof and Type Casting

Staying with our list of the stock we have, let's imagine we want to find all of the trucks that we have in stock, disregarding any other kinds of vehicles that we might have.

This is what the instanceof keyword can be used for: Using it, you can ask any object if it derives of a certain class; that is, if any object is an instance of the specified class.

Let's create a method to return a list of all of the cars we have in stock:

import java.util.ArrayList;

public class Main {
    private static ArrayList<Vehicle> stock = new ArrayList<>();

    public static void main(String[] args) {
        stock.add(new Car(4));
        stock.add(new Truck(10));
        getTrucks();
    }

    private static void getTrucks() {
        for (int i = 0; i < stock.size(); i++) {
            Vehicle vehicle = stock.get(i);
            if (vehicle instanceof Truck) {
                System.out.println("Vehicle " + i + " is a truck!");
            }
        }
    }
}

As you can see, I've expanded the code from before to have a getTrucks method, which iterates through every vehicle we have in stock and if it finds a vehicle that is an instance of the Truck class, it will print out a message.

Another way to think about the instanceof keyword is as a weird, clunky synonym to the word "is": When asking vehicle instanceof Truck, we're really asking "Is this vehicle a truck?"

Now that we know which of the vehicles we have in stock are trucks, it might also be useful to know the storage capabilities of all of those trucks. However, as you can see, the ith vehicle we're currently inspecting is always of typ Vehicle, as that's what we store in the stock list. That means that we won't be able to access any Truck's storageArea variable. That's what the concept of type casting can be used for.

If you have a variable of a certain type, but you know for a fact that it's actually an instance of a child class of that type, then you can change that variable to be of the child class' type instead like so:

Vehicle vehicle = stock.get(i);
if (vehicle instanceof Truck) {
    Truck truck = (Truck) vehicle;
    System.out.println("The " + i + "th truck can store " + truck.storageArea);
}

As you can see, to type cast a variable into a different type, all you have to do is write that type in parentheses () in front of the variable.1 Cool.

Conclusion

So today, you learned one of the important aspects of object oriented programming, which is very useful in a heap of different scenarios, as you will be able to see throughout the next tutorials. Next time, we'll be covering overriding methods from superclasses, which will allow you to do even more cool stuff with inheritance.2

I'm sorry that I've been taking such a long break from the tutorials, but after asking on Twitter if you people actually learn from them, a lot of you said that you do, and so that gave me the motivation to continue working on these.

So thanks a lot, and happy coding! <3


  1. As you might be able to see now, this concept is called type casting because it's like we're putting the vehicle into a mold (or a cast) to reshape it into a different type. ↩︎

  2. I was originally planning on including that in this tutorial already, but I noticed that that might be a bit too much after all, so I'll be doing it next time instead. ↩︎