7. Inheritance - ZolotovaNatalia/JavaCourse_2017 GitHub Wiki

Inheritance

Inheritance is one of the most important features in Object-Oriented Programming (OOPs). Inheritance allows a class to use the properties and methods of another class. In other words, the derived class inherits the states and behaviors from the base class. The derived class is also called subclass or child class and the base class is also known as super-class or parent class. The sub class can have its own additional variables and methods. They differentiate the sub class from the parent class.

Let's go back to the example with the Street that has a Bus.

Suppose we want to control not only buses at the street but also city trains. Let's create a new type Train and add fields and methods like the Bus has:

public class Train {
    private int id;
    private int maxAmountOfPassengers;
    private String brand;
    private String color;
    private Passenger[] passengers;
    private int amountOfPassengers = 0;
    
    public Train(int id, int maxAmountOfPassengers, String brand, String color){
        this.id = id;
        this.maxAmountOfPassengers = maxAmountOfPassengers;
        passengers = new Passenger[maxAmountOfPassengers];
        this.brand = brand;
        this.color = color;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getMaxAmountOfPassengers() {
        return maxAmountOfPassengers;
    }

    public String getBrand() {
        return brand;
    }

    public String getColor() {
        return color;
    }

    public Passenger[] getPassengers() {
        return passengers;
    }

    public void addPassenger(Passenger passenger){
        if(amountOfPassengers < maxAmountOfPassengers){
            passengers[amountOfPassengers] = passenger;
            System.out.println("Passenger " + passenger.getName() + " entered");
            amountOfPassengers++;
        }else {
            System.out.println("Passenger " + passenger.getName() + " should take another transport");
        }
    }
}

Here we can see that Train is basically a copy-paste of the Bus. If we need to create another transport like airplane or a ship, we will have to cope-paste several times. Such approach is not effective, the code is full of duplications. As soon as your code will be growing it will be difficult to maintain it. For example, if you need to change some behavior and the changes should affect all transport classes, you have to make changes in all of them : in Bus, Train, Airplane an so on.

In Java Inheritance was invented to resolve such issues.

We create a parent class the keeps all common behavior for the transport. Let's call it Transport and put there all properties and methods that are shared between Bus and Train:

public class Transport {
    private int id;
    private int maxAmountOfPassengers;
    private String brand;
    private String color;
    private Passenger[] passengers;
    private int amountOfPassengers = 0;

    public Transport(int id, int maxAmountOfPassengers, String brand, String color) {
        this.id = id;
        this.maxAmountOfPassengers = maxAmountOfPassengers;
        passengers = new Passenger[maxAmountOfPassengers];
        this.brand = brand;
        this.color = color;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getMaxAmountOfPassengers() {
        return maxAmountOfPassengers;
    }

    public String getBrand() {
        return brand;
    }

    public String getColor() {
        return color;
    }

    public Passenger[] getPassengers() {
        return passengers;
    }

    public void addPassenger(Passenger passenger) {
        if (amountOfPassengers < maxAmountOfPassengers) {
            passengers[amountOfPassengers] = passenger;
            System.out.println("Passenger " + passenger.getName() + " entered");
            amountOfPassengers++;
        } else {
            System.out.println("Passenger " + passenger.getName() + " should take another transport");
        }
    }
}

To use this class we need to show Java which classes are children and which is a parent. For the there is a keyword extends. Apply this to the Bus class:

public class Bus extends Transport{
    public Bus(int id, int maxAmountOfPassengers, String brand, String color){
        super(id, maxAmountOfPassengers, brand, color);
    }
}

We created Bus as a Child that inherits everything from a parent class Transport. That's why there is no need to keep all methods and fields in the Bus that we moved to the parent class. Constructor is also modified. Instead of initialization of the local fields we are passing the values to the parent class where they are stored. Now we do the same with the Train:

public class Train  extends Transport{
    public Train(int id, int maxAmountOfPassengers, String brand, String color){
        super(id, maxAmountOfPassengers, brand, color);
    }
}

The main thing in all these changes that the users of these classes don't see any difference in object creation, using its fields and methods. Let's go beck to the class Street:

public class Street {
    public static void main(String[] args) {
        Bus bus = new Bus(1, 20, "BMW", "black");
        Passenger passenger1 = new Passenger(1, 10,"Peter");
        bus.addPassenger(passenger1);

        Train train = new Train(1, 100, "Symence", "red");
        Passenger passenger2 = new Passenger(1, 10,"Oleg");
        train.addPassenger(passenger2);
        train.addPassenger(passenger1);
    }
}

We can still create objects Bus, Train and add passengers there as before. The only thing that is changed is the implementation behind Bus and Train. But the users don't have to know about them. That is the example of Encapsulation and a big benefit of the Inheritance.

Implement specific fialds

Since Train and Bus are totally different kinds of transport, they have their own behavior and state. For example, the train has cars. We can add to the Train class field with the new type TrainCar (to distinguish from usual Car) or an array with cars. We don't add them to the Transport class because cars shouldn't be inherited by Bus.

public class Train  extends Transport{
    
    private TrainCar[] trainCars;
    
    public Train(int id, int maxAmountOfPassengers, String brand, String color, int amountOfCars){
        super(id, maxAmountOfPassengers, brand, color);
        trainCars = new TrainCar[amountOfCars];
    }

    public TrainCar[] getTrainCars() {
        return trainCars;
    }
    
    public void addTrainCars(int newCars){
        trainCars = new TrainCar[trainCars.length + newCars];
    }
}

We define methods getTrainCars() and addTrainCars() in the Train class, not in the Transport, because they refer to the private field trainCars that is not accessible in the Transport class. But now in the Street class we should pass amountOfCars to the Train constructor:

Train train = new Train(1, 100, "Symence", "red", 10);
train.addTrainCars(3);
train.getTrainCars();

Exercise 1

Implement another method in the Train class removeTrainCars().

Exercise 2

Add a new field to the Bus class and implement a method that modifies it or perform some behavior using it. This method shouldn't be available from the Transport or Train classes.