Real Time Systems

Exercise 0: Basic Object-Orientation in Java

2019 Version


Introduction

The aim of this exercise is to give a short overview of the basic object-oriented concepts in Java. The intended audience is students with limited previous experience of Java or students that have progammed in Java, but mainly have used the non object-oriented parts of the language.

Objects

A real world object has two characteristics: state and behavior. The state of a car could be its brand, model, colour, registration number, speed, gear etc. The behavior of a car may include start, stop, accelerate, brake, turn right, etc. The main idea of object-oriented programming is to model the physical objects that the application concerns as software objects. For example, a program that deals with cars would make use of car objects. Software objects also have state and behavior. A Java software object stores its state in its fields and exposes its behavior through its methods. The methods operate upon the object's internal state by accessing the values of its fields. Hiding internal state and requiring all interaction to be performed through an object's methods is known as data encapsulation — a fundamental principle of object-oriented programming.

Classes

In the real world one normally finds many objects that are all of the same kind. For example, there are numerous cars, but they all share the same main characteristics. The common characteristics are gathered in the class description for the objects. In object-oriented terms, we say that each individual car is an instance (or instance object) of the class of objects known as cars.

The following is a very trivial definition of the Car class (in Java, classes are normally named by nouns and their first character is capitalized). It should be contained in a file called Car.java:

public class Car {
    private String registrationNumber = "";
    private String model = "";
    private double speed = 0.0;

    public Car(String number, String carModel) {
        registrationNumber = number;
        model = carModel;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }
}

The first method is the constructor for the class. It is called when a new instance of the class is created. The two other methods are known as access methods. They are used to get and set the value of a field.

The above class is not a complete application. It is only a definition that may be used in an application. For example, the following statements are used to create two cars:

...
Car car1;
Car car2;
car1 = new Car("ABC111", "Volvo V70");
car2 = new Car("DEF222", "Saab 9-5");
car1.setSpeed(100.0);
System.out.println("The speed of car1 is " + car1.getSpeed());
...

Above, car1 and car2 are references that may refer to instances of the Car class. Before the 'new Car(...)' statements these references point to null. In each 'new Car(...)' statement a new instance of the Car class is created, the constructor of the class is called, and a reference is set to refer to (point at) the instance. Hence, after executing the above code two instances of the Car class exist, one of them is referenced by car1 and the other by car2.

The main method

In order to create an executable application a class with a main method is needed. A common approach is to use a separate Main class for this, see below

public class Main {
    public static void main(String[] args) {
        Car car1;
        Car car2;
        car1 = new Car("ABC111", "Volvo V70");
        car2 = new Car("DEF222", "Saab 9-5");
        car1.setSpeed(100.0);
        System.out.println("The speed of car1 is " + car1.getSpeed());
    }
}

The main class should be contained in a file called Main.java. Similarly, the Car class should be contained in the file Car.java. The above example is compiled and executed using the following statements:

> javac *.java
> java Main
  1. Write the two classes Car and Main. Do not copy and paste. Use a text editor that you are familiar with (xemacs, gedit, ...). Compile and execute the application.
Solutions and how to compile and run is shown above.

Alternatively the main method could be made a part of the Car class. In that case it should be this class that is executed.

Static fields and methods

Normally, each instance of a class has its own local version of the fields declared in the class definition. However, in some cases it is more natural to let all the instances of the same class share a field. This is obtained if the field is declared as static. For example, assume that the above Car class is used in an application where one is interested in keeping track of how many Car instances that have been created. This can be obtained in the following way:

public class Car {
    private String registrationNumber = "";
    private String model = "";
    private double speed = 0.0;
    private static int numberOfCars = 0;

    public Car(String number, String carModel) {
        registrationNumber = number;
        model = carModel;
        numberOfCars++;
    }

    public int getNumberOfCars() {
        return numberOfCars;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }
}

A new static field, numberOfCars, has been added. This is incremented in the constructor. The number of cars is information that relates to the Car class rather than to any of the individual car instances. Therefore, static fields are also known as class fields or class attributes. The value of a static field can be accessed through any of its instances, e.g. car1.getNumberOfCars(). However, what should one do if no cars have been created yet? In this case there are no instances that can be used to access the static field. The solution to this is to declare the getNumberOfCars() method as static:

    public static int getNumberOfCars() {
        return numberOfCars;
    }

A static method can be called directly through the class. In the above example one would use the statement Car.getNumberOfCars(). A static method may only access static fields. This is quite natural since it should be possible to call the method without refering to any particular instance. The main method is an example of a static method. When an application is started through > java Main this corresponds to a call to Main.main().

  1. Add the static field numberOfCars to the Car class. Modify the main method on the Main class so that it makes use of the getNumberOfCars method. Create a few cars, call getNumberOfCars(), and print the results.
Car.java:
public class Car {
    private String registrationNumber = "";
    private String model = "";
    private double speed = 0.0;
    private static int numberOfCars = 0;

    public Car(String number, String carModel) {
        registrationNumber = number;
        model = carModel;
        numberOfCars++;
    }

    public static int getNumberOfCars() {
        return numberOfCars;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }
}
Main.java:
public class Main {
    public static void main(String[] args) {
        Car car1;
        Car car2;
        car1 = new Car("ABC111", "Volvo V70");
        car2 = new Car("DEF222", "Saab 9-5");
        car1.setSpeed(100.0);
        System.out.println("The speed of car1 is " + car1.getSpeed());
	System.out.println("We've so far created " + Car.getNumberOfCars() + " cars.");
    }
}

Inheritance

Different kinds of objects often have some features (fields and methods) in common. For example, cars and trucks are all vehicles. The shared features of all vehicles are typically modeled in a separate Vehicle class. It is then possible for both the Car class and the Truck class to inherit these features from the Vehicle class. It is also possible to add new features (fields and methods) in the Car and Truck classes that are specific to cars and trucks, respectively. We now say that the class Vehicle is the super class of the classes Car and Truck. We also say that the classes Car and Truck extend the class Vehicle.

Study the following piece of code. Note that we have moved the contents of the previous Car class to the new Vehicle class.

// ----- In file Vehicle.java -----------------------

public class Vehicle {
    private String registrationNumber = "";
    private String model = "";
    private double speed = 0.0;
    private static int numberOfVehicles = 0;

    public Vehicle(String number, String vehicleModel) {
        registrationNumber = number;
        model = vehicleModel;
        numberOfVehicles++;
    }

    public static int getNumberOfVehicles() {
        return numberOfVehicles;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }
}

// ----- In file Car.java ----------------------------

public class Car extends Vehicle {
    private int numberOfPassengers = 0;

    public Car(String registrationNumber, String carModel) {
        super(registrationNumber, carModel);
    }

    public int getNumberOfPassengers() {
        return numberOfPassengers;
    }

    public void setNumberOfPassengers(int passengers) {
        numberOfPassengers = passengers;
    }
}

// ----- In file Truck.java ---------------------------

public class Truck extends Vehicle {
    private int currentLoad = 0;

    public Truck(String registrationNumber, String truckModel) {
        super(registrationNumber, truckModel);
    }

    public int getCurrentLoad() {
        return currentLoad;
    }

    public void setCurrentLoad(int load) {
        currentLoad = load;
    }
}

In Java a class may only have one super class. This is known as single inheritance.

An instance of the Car class is indirectly also an instance of the Vehicle class. The same holds for instances of the Truck class. This means that it is possible to use a reference to vehicles to refer to both cars and trucks, according to the following:

public void printSpeed(Vehicle v) {
    System.out.println("Speed = " + v.getSpeed());
}

This method could be used with both car and truck references as arguments. It would also be possible to declare an array to contain references to vehicles. The contents of this array could then contain references to both cars and trucks.

  1. Implement the Car, Truck, and Vehicle classes. Write a Main class that declares and calls the printSpeed method. Note that the printSpeed method must be declared as static since there is no instance of the Main class that it can be called through.
In Main.java:
public class Main {
    public static void printSpeed(Vehicle v) {
        System.out.println("Speed = " + v.getSpeed());
    }

    public static void main(String[] args) {
        Vehicle car;
        Vehicle truck;
        car = new Car("ABC111", "Volvo V70");
        truck = new Truck("DEF222", "Saab 9-5");
        car.setSpeed(100.0);
        truck.setSpeed(80);
        printSpeed(car);
        printSpeed(truck);
    }
}
Car.java, Truck.java and Vehicle.java are shown in the examples above.

Interfaces

The fact that a class only may have one super class creates certain limitations. For example, assume that we in our class hierarchy also have a class representing marathon runners. The MarathonRunner class also has the methods getSpeed and setSpeed. However, the MarathonRunner class does not inherit from the Vehicle class. Assume now that we want to write a method called printSpeed that we should be able to use to print the speed of both vehicles and marathon runners. How should this be solved? If the Vehicle class and the MarathonRunner both have a common super class somewhere in the inheritance tree, then one option would be to move the fields and methods that have to do with speed to this super class.

A better solution in this case is to use an interface. An interface is a collection of methods that a class guarantees that it will implement. In our example the methods that need to be included in the interface are the methods that relate to speed (actually it is only getSpeed, but for the sake of clarity we add both the setSpeed and getSpeed methods). We will call an object that has a speed Movable. This will be the name of the interface, see below

// ----- In Movable.java --------------

public interface Movable {
    public double getSpeed();
    public void setSpeed(double newSpeed);
}

We must now modify our declaration of the Vehicle class slightly. To indicate that it now implements the Movable interface, see below:

public class Vehicle implements Movable {
    // The rest as before
}

We also must declare the class MarathonRunner to implement the Movable interface. Additionally, the methods in the Movable interface must be implemented, see below:

public class MarathonRunner implements Movable {
    private double speed = 0.0;

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }

    // additional fields and methods
}

With this done, it is now possible to write the following method:

public void printSpeed(Movable v) {
    System.out.println("Speed = " + v.getSpeed());
}

Here, v is declared to be a reference to a Movable. It is possible to declare references to objects that implement an interface in the same way as it is possible to declare references to instances of a class:

Car car;
Movable m;

It is, however, not possible to create a new instance of an interface:

Movable m = new Movable();

The following is, however, allowed:

Car car = new Car("GHI333", "Ford Escort");
Movable m = car;

A class may only extend one superclass, but it may chose to implement several interfaces.

  1. Add the Movable interface and the MarathonRunner class to your code. Modify the main class to exemplify the above discussion.
In Movable.java:
public interface Movable {
    public double getSpeed();
    public void setSpeed(double newSpeed);
}
In MarathonRunner.java:
public class MarathonRunner implements Movable {
    private String name;
    private double speed = 0.0;

    public MarathonRunner(String name) {
        this.name = name;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }

    public String getName() {
        return name;
    }
}
In Vehicle.java:
public class Vehicle implements Movable {
    private String registrationNumber = "";
    private String model = "";
    private double speed = 0.0;
    private static int numberOfVehicles = 0;

    public Vehicle(String number, String vehicleModel) {
        registrationNumber = number;
        model = vehicleModel;
        numberOfVehicles++;
    }

    public static int getNumberOfVehicles() {
        return numberOfVehicles;
    }

    public void setSpeed(double newSpeed) {
        speed = newSpeed;
    }

    public double getSpeed() {
        return speed;
    }
}
In Main.java:
public class Main {
    public static void printSpeed(Movable m) {
        System.out.println("Speed = " + m.getSpeed());
    }

    public static void main(String[] args) {
        Movable car;
        Movable truck;
        Movable runner;
        car = new Car("ABC111", "Volvo V70");
        truck = new Truck("DEF222", "Saab 9-5");
        runner = new MarathonRunner("Anton");
        car.setSpeed(100.0);
	truck.setSpeed(80.0);
        runner.setSpeed(30.0);
	printSpeed(car);
	printSpeed(truck);
        printSpeed(runner);
    }
}
Car.java and Truck.java doesn't need to be changed.

Access Modifiers

In the code examples above, the word public is used here and there. This is an example of an access modifier. An access modifier decides which other classes that may access your code. In Java, access modifiers can be applied to classes and to the contents of classes, i.e., fields and methods. Classes are normally declared to be public, meaning that the class is accessible to all classes everywhere. Without the public modifier the access to the class is limited. In the exercises all your classes should be declared public.

For controlling the access to the contents of classes, i.e., fields and methods, the two most important access modifiers are public and private. An access modifier determines which other classes that may access your field or method. The following rules apply:

A public field can be accessed directly using dot notation. For example, in the above code example it would be possible to directly access the speed field without using the getSpeed or setSpeed methods if it were declared public. For example, the following example would be allowed:

    Car car1 = new Car("GHI333", "Ford Escort");
    car1.speed = car1.speed + 50.0;

A class without any methods and with only public fields corresponds to a structured data type in non-object oriented languages, e.g. a C struct.

It is good object-oriented practice to use the private modifier for all fields and use public access methods (get and set methods) to access these fields.

Below the surface

The memory organization of Java classes can be described in the following simplified way: