Lecture 3

It did not take long to create our first programs, but they are still very basic. They consisted of only one or two methods that compute and return something. For more interesting problems we need to know how to create and manipulate more complex data structures. Here is where the objects in Object Oriented Programming (OOP) come to play a role. In the previous examples we only used int and double variables. They both refer to just a single memory cell and they can keep only a single value. By using objects we can bundle together multiple values.

Computationally an object is a piece of memory which keeps the values of several variables. The insight of object oriented design, however, is that we should not think on the level of memory cells, but we should take a more abstract point of view where each object in the program models a real-world thing with its attributes and behaviour. For examples things in the real world are described by their colour, height, width, depth, current position, duration, name, etc. Computationally all this attributes can be represented as values of variables. The behaviour of the objects on the other hand is described by using methods. The inovation of object oriented programming is that programs (i.e. behaviour/methods) and data (i.e. attributes/variables) should be bundled together.

Objects are always associated with a certain class. The class describes the variables and the methods that are available for every object, while the object is the actual thing. The class does not contain any values, just variable definitions. The actual values are stored in the concrete objects. Before we can create an object, we first need to have its class definition. The class definition is used by the compiler, for, among other things, to compute how much memory each object should take.

The basic concepts of Object Oriented Programming are nicely described in the following video:

Let's take something very basic to start with. We will model the gadget on the picture bellow:

This is a counter which people can use for counting different things. You just press a button and this increases the number on the display. In addition we can reset the counter to zero and start counting again. This is the important functionality that we want to model on a computer. All other aspects like the color and the shape of the counter, we consider irrelevant and they will be ignored by the program. The first thing to do is to create a Java class which describes the common properties of the different counters:

public class Counter {
     private int count;
}

The declaration 'int count' inside the currly braces means that every object of type Counter is characterized by one integer, i.e. 'int' in Java. Remember, we said that classes describe the common properties of all objects but still every object has its own individuality. For example, if you have two different counters then each will show a different number. In object oriented languages this is modelled by using instance variables. In the previous lecture we have used variables, but they were used only locally in the body of a method. Such variables are called local variables. Every time when a method is called, a block of memory is allocated for storing its local variables. This memory is used only during the method execution and after that it is released and possibly reused for other methods. The instance variables are different. They are defined inside the class definition and not inside a method. When we create an object of this class then the interpreter will allocate memory for its variables and this memory will be retained as long as the object is used. Furthermore, these variables can be used from all methods of the same class.

The instance variables need to be initialized. Think about it in this way: When you buy a new counter in the real world, it will show some predefined number (zero for instance) which is set by the manifacturer. It is the same in programming. When your program creates a new counter it must set the current count to some value. This is done by using a special kind of methods called constructors. They are defined in a way very similar to the ordinary methods except that their name must be exactly the same as the name of the class itself. The following adds a constructor which initializes the count to zero:

public class Counter {
    private int count;

    public Counter() {    // this is the constructor
        count = 0;
    }
}

The definition of the constructor must look familiar to you since its syntax is quite similar to the definition of the main method in the First.java application. The only difference is that while for normal methods we also specify either a return type (i.e. int or double) or void, here we do not write anything. The reason is that by definition the constructor cannot return a value. It just initializes the current object.

The assignment statement inside the constructor initializes the instance variable count. Note that before we both defined and initialised the variable inside the method body. Here we define it inside the class, while in the constructor we just set its value. The statement 'count = 0' says that the instance variable 'count' must get an initial value equal to zero.

Now we can add three more regular methods which will perform the different operations that we can do with a counter, i.e. we can increment the counter, we can reset it to zero, and we can read the current value. The implementation is bellow:

public class Counter {
    private int count;

    public Counter() {
        count = 0;
    }

    public void increment() {
        count = count + 1;
    }

    public void reset() {
        count = 0;
    }

    public int getValue() {
        return count;
    }
}

You should have learned enough so far to be able to understand the implementation of the methods 'increment', 'reset' and 'getValue'. The latter is actually doing a very similar thing as the constructor. It just resets again the count to zero. The increment method assigns a new value which is equal to the current value plus one. getValue returns the current value of the counter.

There is one difference that you may have noticed. In the previous lecture we used to put the keyword static immediately after the keyword public for each method definition. Here we do not see it anymore. The point is that when we execute a method it can access both its local variables as well as the instance variables in the same class. This means that before we can execute a method we first must have created an object for which to call the method. If we mark a method as static then the method is not allowed to access the instance variables. On the other hand this makes it possible to call a static method even before an object has been created. The main method in the previous examples is the first method that is ever executed, even before any object has been created. For that reason the main method is always static. We will discuss this again later.

We can now do a simple test of the Counter class. For this we create a new class called 'CounterTest' which has a main method. In the main method we create two counters. The first is incremented twice and the second only once. At the end we print the current value of each counter:

public class CounterTest {
    public static void main(String[] args) {
        Counter counter1 = new Counter();
        counter1.increment();
        counter1.increment();
        Counter counter2 = new Counter();
        counter2.increment();

        System.out.println(counter1.getValue());
        System.out.println(counter2.getValue());
    }
}

Here the creation of the counter objects is done with the operator 'new'. The operator creates a new object of the specified class and automatically calls the constructor for the class. This ensures that the initial value for each counter is set to zero. We can use the assignment operator to bind each of the two newly created counters to some variable, i.e. 'counter1' and 'counter2'. Note that now we use the class name for declaring the variables rather than int or double like before. After that we call the increment. Finally we print the current values of the two counters. When you run the application, you will see that the values of the two counters are different. This is the most important property of the instance variables.

Finally a notice about the keywords 'public' and 'private' that you have seen many times so far but we have left without explanation. The reason for this is that we needed an application with at least two classes in order to demonstrate how to use these keywords. Try to run the following code which tries to set an illegal negative value to the counter:

public class CounterTest2 {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.count = -2;
    }
}

The compiler will refuse to compile the program with the error:

CounterTest2.java:4: count has private access in Counter
        counter.count = -2;
               ^

By declaring the count variable as private we tell the compiler that access to this variable is allowed only from methods in the same class. In our case by using only the methods declared in the class we can be sure that the counter is always a positive number just like in the physical counter. However, if we don't declare the variable as private then nothing will prevent some external malicious code to set an invalid value. Think about the private variables as something internal that you want to hide from the users of your code. In principle it is a good idea to always declare all instance variables as private, unless you find a good reason not to do so.

Inheritance

So far we have implemented two simple classes with few methods and few instance variables, but this doesn't really show the benefits of using object oriented programming. Where OOP really pays off is its ability to structure our code in way which allows us to reuse existing code.

We will incrementally develop a new kind of counters which in addition to being able to count objects are also uniquely identified with a serial number. Most hardware producers put a stamp on the gadgets that they produce in order to identify them later if for example some defect is found. Our software model will have the same property.

We don't want to copy the code from Counter class since this leads to duplicated code and at the end it makes the code more difficult to maintain. The solution that OOP provides is to define classes which are extensions of other classes. The new class will have the same functionality as the previous one but in addition it will provide some extra features. This is how we define an extension:

public class SerialCounter extends Counter {
}

This will declare a new class SerialCounter which will automatically inherit the definitions of methods increment(), reset() and getValue() from the counter class. We specify it with the keyword extend and the name of the parent class Counter. The standard terminology in OOP is that SerialCounter inherits Counter, or in the other direction we say that Counter is the parent class of SerialCounter.

The new class still does not add anything new but we are free to declare new methods and instance variables. This is our first attempt to implement counters with serial numbers:

public class SerialCounter extends Counter {
    private String serNo;

    public SerialCounter() {
        serNo = "";
    }

    public String getSerialNumber() {
        return serNo;
    }

    public String setSerialNumber(String no) {
        serNo = no;
    }
}

Here we use the new type String which is the type of objects that store textual values. Any text surrounded in double quotes (") is a value of type string. We will talk more about that in the next lecture. The serial number is a string which is stored in a new instance variable. We can verify that the methods from the old Counter class still work as usual but in addition we are allowed to do more, i.e. we can do something like this:

SerialCounter counter = new SerialCounter();
counter.setSerialNumber("CNT123");
counter.increment();
counter.increment();
System.out.println(counter.getSerialNumber()+": "+counter.getValue());

The serial counters are also usable in all contexts where a simple counter is required. For instance we can assign a reference to SerialCounter to a variable of type Counter:

SerialCounter serial_counter = new SerialCounter();
Counter counter = serial_counter;
counter.increment();

The code above will be accepted and it will work normally.

Static Methods and Variables

The new kind of counters share the functionality with the simple counters but this is still not a very faithful model of the real world counters. The problem is that in the real world the producer assigns serial numbers which are unique and they cannot be changed once the gadget has been produced. In our model this doesn't hold since we can call the method setSerialNumber with any value and as many times as we want. This means that the numbers are neither unique nor constant. In order to model this kind of behaviour we need to use another OOP feature. Look at the new implementation:

public class SerialCounter extends Counter {
    private String serNo;

    private static int lastSerNo = 0;

    public SerialCounter() {
        lastSerNo = lastSerNo + 1;
        serNo = "CNT" + lastSerNo;
    }
    
    public String getSerialNumber() {
        return serNo;
    }
}

The setSerialNumber method is now removed. Instead the constructor sets the serial number and after that it is never changed. We can only read by calling getSerialNumber.

How to guarantee that the serial numbers are unique? There is one more variable in the class called lastSerNo. Unlike serNo it is declared with the keyword static. This means that the variable is shared between all objects of the same class. The following picture should make it clearer:

There are two different different objects of class SerialCounter and each object has its own copy of instance variables like count and serNo. When we declare some variable as static then instead we store it a single place and it is shared between all objects. Each time when we create an object we increment the variable lastSerNo and then we use its current value to assign a serial number to the new object. In this way the objects will get serial numbers such as CNT1, CNT2, etc.

Our intention is that once we assign a serial number to the counter, it should never change anymore. There is one keyword that let us to make our intention of having immutable instance variables explicit. We can redefine serNo in this way:

private final String serNo;

The keyword 'final' tells the compiler to reject any code that attempts to change the instance variable after it has been initialized in the constructor. This means that even in principle we are not allowed to add methods like setSerialNumber.

When final is used with instance variables then it just makes our intention more explicit and prevents us from changing the value accidentially. It is a lot more often used in combination with the keyword static since this let us to define constants in Java. For example instead of having the serial number prefix "CNT" burried in the code it is a lot cleaner to define it as a constant inside the SerialCounter class:

public static final String SERIAL_NUMBER_PREFIX = "CNT";

The constants are exactly this: values which are shared among all objects and are immutable. The combination static final gives us the right semantics.

More about Instance and Static Variables

Inside a method we can access both instance variables, static variables and methods defined in the same class by just citing the variable or method name. What happens then is that we manipulate the variables of one particular instance which is implicit in the context. If we want to access a variable or a method from another instance then we must explictly point to it. For instance if we call:

counter.increment()

then the method increment will be executed in the context of the instance referenced from the variable counter. How does the method know which instance it must deal with? Each method can have one or more explict arguments which are the input to the method. However, there is also one implicit argument called this which is a reference to the instance which is the context for all instance variables. We can also choose to make this explicit. For example, we may choose to reimplement the increment method as follows:

public void increment() {
    this.count = this.count + 1;
}

This is an equivalent but just more verbose definition of the same method. In fact this is pretty silly use of the keyword this.

There are, however, two quite frequent uses of this. Imagine that for some reason we again want to make it possible to create a counter with a specific serial number rather than the automatically generated one. We can naively provide a constructor like:

public SerialCounter(String serNo) {
    serNo = serNo;
}

But there is a problem, now the name serNo can refer both to the argument of the constructor and to the instance variable. By default the Java compiler will resolve serNo as a reference to the argument. If we want to access the instance variable as well then we have to either rename the argument, or we can access the instance variable explicitly as this.serNo:

public SerialCounter(String serNo) {
    this.serNo = serNo;
}

It is a convention that when a constructor takes as arguments the initial values of some instance variables, then the arguments should have the same names as the names of the instance variables. After that the initialization itself should be done as above.

The other common use of the keyword this is when a method wants to pass a reference to its object to a method of another object. Consider that we want to define the following slightly contrived method to a class:

public void printMe() {
    System.out.println(this);
}

Here the method println will execute in the context of System.out but as an argument it will get a reference to the object which is the context of printMe.

The concept of context also makes clearer the notion of static variables. While the instance variables are stored in the object referenced with this, the static variables are global. Their only connection with the class in which they are defined is that the class defines the visibility of the variable. For instance a private variable is visible only to the methods defined in the same class. A public variable is visible to other classes as well but then it must be accessed by citing both the name of the class and the name of the variable. For example, it is possible from any class to access the constant SERIAL_NUMBER_PREFIX, but you must write it as:

SerialCounter.SERIAL_NUMBER_PREFIX

The reason is that it is completely possible that there are variables with the same names defined in different classes. In order to disambiguate we must use the class name as well.

Just like static variables, we could also have static methods. The difference between instance methods and static methods is that the later does not have the implicit argument this. This means that they cannot access instance variables because they do not execute in the context of any object. They could, however, access static variables and other static methods.

Speaking about object references we must also mention the special reference null. Most of the work in a object oriented program is to manipulate objects and to send messages between them. It is often useful to indicate that a variable is not associated with any object yet. This is exactly the use for the keyword null. Any variable of object type could be initialized with null, for example:

Counter c = null;

Will declare the variable c but it will not associate it with any object, on the contrary by assigning null we indicate that it points to nowhere. Any attempt to call a method or access an instance variable from a variable with null value will lead to a runtime error.

The Math class

The static methods in Java play the role of what is called functions, procedures or subroutines in other languages. In other words this are pure computations which are not associated with any objects. In the Java standard libraries there are several classes whose purpose is solely to be containers for commonly used functions and constants. A typical example is the class Math. This is a class which provides several standard mathematical functions such as sin, cos, exp, etc. Since this are static methods we must call them from the Math class. For instance we can compute sine of 2π as follows:

double x = Math.sin(2*Math.PI);

In addition to the traditional mathematical functions there are also other small utilities useful for programming. For instance in Lab 2 we are going to use the function random which generates a new random number from 0 to 1 each time when it is called. Try something like this:

double x = Math.random();

If you need a random number in another range, let's say from a to b then you need a simple arithmetic:

double x = a + (b-a)*Math.random();