151 lines
No EOL
8.9 KiB
Markdown
151 lines
No EOL
8.9 KiB
Markdown
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:
|
|
```java
|
|
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:
|
|
```java
|
|
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:
|
|
```java
|
|
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.
|
|
```java
|
|
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:
|
|
```java
|
|
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:
|
|
```java
|
|
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:
|
|
```java
|
|
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:
|
|
```java
|
|
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 `i`th 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:
|
|
```java
|
|
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. |