Using objects in Java: the drinks machines examples, part 2

Aliasing

One of the most important things to understand about Java is that variables hold references to objects, they do not hold objects themselves. You have already seen how this works with arrays, but arrays are just a special type of object, the same applies to all other objects. Since variables hold references to objects and not objects themselves, two or more variables may hold references to the same object, this is referred to as aliasing.

Aliasing only has an effect if the internal state of objects may be changed. The internal state of an object is changed by calling a method on it which changes the state of the object. In the DrinksMachine example, we saw that several of the methods that could be called on a DrinksMachine object changed its internal state. We can only tell that the internal state of an object is changed by calling a method on it which displays or returns a value. As an example, two of the methods in class DrinksMachine had signatures:

int getPrice()
void setPrince(int p)
The first method, getPrice gives the price which the DrinksMachine object it is called on sells drinks for. It doesn't change the state of the DrinksMachine object, so if we have a DrinksMachine object referred to by variable mach1, every time we call mach1.getPrice() we will have the same integer value returned so long as we never call on mach1 a method which changes the state of the object it is called on.

The second method, setPrice, changes the price which the DrinksMachine object it is called on sells drinks for. So this does change the internal state of the object it is called on. If we run the following code fragment we can see that:

  DrinksMachine mach1 = new DrinksMachine(50);
  System.out.println("Here the price is "+mach1.getPrice()+"p");
  mach1.setPrice(60);
  System.out.println("Now here the price is "+mach1.getPrice()+"p");
When we call mach1.getPrice() the first time, we get the value 50 returned, when we call it the second time, after mach1.setPrice(60) has been executed, the value 60 is returned.

Aliasing means that when we execute an assignment statement, such as mach2=mach1, the variable on the left hand side of the = sign stops referring to what it referred to before and starts referring to what the variable on the right hand side refers to. The variable on the right hand side continues to refer to what it referred to before, so the effect is that mach1 and mach2 refer to the same object. Executing the following code illustrates this:

  DrinksMachine mach1 = new DrinksMachine(60);
  DrinksMachine mach2 = new DrinksMachine(50);
  System.out.println("Here price 1 is "+mach1.getPrice()+"p");
  System.out.println(" and price 2 is "+mach2.getPrice()+"p");
  mach2=mach1;
  System.out.println("Now here price 1 is "+mach1.getPrice()+"p");
  System.out.println("     and price 2 is "+mach2.getPrice()+"p");
At first, price 1 is 60p and price 2 is 50p. After the execution of mach2=mach1, price 1 is 60p and price 2 is 60p. This isn't because any object has had its internal state changed, rather it's because mach2 has been caused to stop referring to the machine that charges 50p and start referring to the machine that charges 60p. Note that executing mach2=mach1 doesn't change what any other variable refers to, even if it refers to what mach2 referred to previously. This can be illustrated by executing the following code:
  DrinksMachine mach1 = new DrinksMachine(60);
  DrinksMachine mach2 = new DrinksMachine(50);
  DrinksMachine mach3 = mach2;
  System.out.println("Here price 1 is "+mach1.getPrice()+"p");
  System.out.println(" and price 2 is "+mach2.getPrice()+"p");
  System.out.println(" and price 3 is "+mach3.getPrice()+"p");
  mach2=mach1;
  System.out.println("Now here price 1 is "+mach1.getPrice()+"p");
  System.out.println("     and price 2 is "+mach2.getPrice()+"p");
  System.out.println("     and price 3 is "+mach3.getPrice()+"p");
Here, mach3 is set to refer to the same object that mach2 originally referred to, the one charging 50p. It still refers to that machine even after mach2=mach1 causes mach2 to stop referring to the machine charging 50p and start referring to the machine charging 60p.

This can be illustrated diagramatically by the following diagrams. Here is the situation before the execution of mach2=mach1

Here is the situation after:

Variables are illustrated by square boxes with their name underneath, objects by uneven shapes. A variable referring to an object is indicated by an arrow coming from the box representing the variable, and leading to the shape representing the object. Because in this case all we are concerned with is the price a machine charges for a drink, the shapes representing DrinksMachine objects are labelled with just this.

Thinking in terms of diagrams like this, we sometimes say that a variable is pointing to an object rather than referring to it, and we may describe a variable as a pointer. It can be seen that executing mach2=mach1 causes mach2 to stop pointing to the object representing the machine which charges 50p and start pointing to the object representing the machine which charges 60p, that being the machine that mach1 points to at the time of execution of the assignment. It doesn't change what mach3 points to, before and after the assignment mach2=mach1 it points to the object representing the machine which charges 50p, which mach2 pointed to before but does not point to after.

We change the internal state of an object by calling a method which changes the internal state on a variable which refers to that object. Since the object itself is changed, even though it is changed by a call on one variable, the same change may be observed by calling a method on another variable which refers to the same object. This can be illustrated by extending our previous code with a call on setPrice on either mach1 or mach2. Since they both refer to the same machine, whatever change is made through one can be observed by calling getPrice on the other. So if we execute the code:

  DrinksMachine mach1 = new DrinksMachine(60);
  DrinksMachine mach2 = new DrinksMachine(50);
  DrinksMachine mach3 = mach2;
  System.out.println("Here price 1 is "+mach1.getPrice()+"p");
  System.out.println(" and price 2 is "+mach2.getPrice()+"p");
  System.out.println(" and price 3 is "+mach3.getPrice()+"p");
  mach2=mach1;
  System.out.println("Now here price 1 is "+mach1.getPrice()+"p");
  System.out.println("     and price 2 is "+mach2.getPrice()+"p");
  System.out.println("     and price 3 is "+mach3.getPrice()+"p");
  mach2.setPrice(70);
  System.out.println("And here price 1 is "+mach1.getPrice()+"p");
  System.out.println("     and price 2 is "+mach2.getPrice()+"p");
  System.out.println("     and price 3 is "+mach3.getPrice()+"p");
the final situation after the execution of mach.setPrice(70) is illustrated by:

and it can be seen that mach2.setPrice(70) changes the price of the machine referred to by mach1 and the machine referred to by mach2 at that point to 70p, because they are the same machine.

The effects of a method call

We have seen three possible effects a method call may have, but there are four in total, you saw the fourth in your first programming course but we have not yet given an example here. The four are:
  1. Return a value.
  2. Change the internal state of the object the method is called on.
  3. Cause something to happen to the environment outside the computer program.
  4. Change the internal state of an object passed as an argument to the method.
A method may have more than one of these effects, we have seen examples of methods on DrinksMachine objects which both change the state of the object they are called on and return a value. Effect 3 covers methods that cause something to be displayed, but includes also methods which read from the screen, methods which read or write to files rather than the screen, and if we were looking at more advanced aspects of Java we could include things like methods which cause communication across networks.

If a class has no methods which have effect 2 (and all its fields are declared as private so they can't be changed from outside the class), we refer to objects of that class as being immutable. This is an important property, since if we know an object is immutable, we know we can safely pass references to it elsewhere without the danger that it gets changed through a reference we had not taken into account when thinking about some other piece of code. Whether assignment causes aliasing or copying is irrelevant for immutable objects, because aliasing only causes different results from copying if it is possible to change the aliased object internally.

Objects which are not immutable are called mutable. Effect 4 above will only work with arguments that are mutable objects. The example you have seen previously is arrays passed as arguments to methods, if the array is changed within the method, that change is observed in the reference to the array outside the method. We know that DrinksMachine objects are mutable, so we can illustrate effect 4 with a method which takes a DrinksMachine object as an argument. Below is the code for an example program, which you can also find in the file UseDrinksMachine2.java in the directory ~mmh/DCS128/code/drinksmachines

import java.util.Scanner;

class UseDrinksMachines2
{
 public static void main(String[] args)
 {
  Scanner input = new Scanner(System.in);
  DrinksMachine machine = new DrinksMachine(50,10,10);
  System.out.print("Enter the sum of money you wish to spend on cokes: ");
  int amount = input.nextInt();
  int cans = spendOnCokes(amount,machine);
  int change = machine.pressChange();
  System.out.println("You have bought "+cans+" cans of coke");
  System.out.println("You have "+change+"p left");
  if(machine.cokesEmpty())
     System.out.println("You have emptied the machine of cokes");
  else
     System.out.println("The machine still has some cokes left");
 }

 public static int spendOnCokes(int sum,DrinksMachine mach)
 {
  int count=0;
  mach.insert(sum);
  while(!mach.cokesEmpty()&&mach.getBalance()>=mach.getPrice())
     {
      mach.pressCoke();
      count++;
     }
  return count;
 }
}
Please note the first line is a Java import statement. Import statements are needed to use the many prewritten classes which are provided with the Java language. In this case, it enables us to use the class Scanner to input an integer. This is what we are using to get input in the place of the commands obtained by the use of extends basic you had in your first programming course. However, you need not be concerned about that at the moment, the main thing here is the method called spendOnCokes. This method takes an int value and a reference to a DrinksMachine object, and returns the number of cans of Coke that can be bought from the drinks machine represented by the object if the amount of money given by the int argument is inserted. It does this, as you can see by the code for the method, by calling the method insert on the DrinksMachine object passed as an argument with the int value as the argument to insert, and then repeatedly calling pressCoke on the object until either the method cokeEmpty does not return false, or the method getBalance returns a value less than the method getPrice. In other words, either the machine runs out of Coke cans, or the money left to spend is less than the price of a can. The changes made to the machine in the method are carried on after the method has finished executing, so for example if the machine is made empty in the method, it is empty after the method. Note that although the method pressCoke returns a Can object, since the code for the method does not make use of the Can objects returned in any way, the call to pressCoke within it occurs as a statement on its own, it is called only to have the effect of changing the internal state of the object referred to by the variable mach which it is called on.

How do methods work?

The method spendOnCokes above is declared as static. Unlike other methods, when a static method is called, it is not attached to any object. If the method is called in the same class where its code is, then to call it you just give the method name, followed by an opening round bracket, followed by the arguments you wish to use separated by commas, followed by a closing round bracket. To call a static method from another class, you must attach the class to the class name, similar to the way a call to a non-static method is attached to a variable name representing an object.

When a Java method is executing, it will be executing in its own environment. The environment is the variables it has access to, with their current values, these variables will be those that are in the signature to the method being executed, plus any further variables which have been declared up to the current point of execution and have not gone out of scope. When a Java method calls another method, that method is executed in its own environment. But the old environment is kept, and when the called method finishes, the code occurring after it in the original method is executed back in the old environment.

In our code above, the call to the method spendOnCokes occurs in the statement:

int cans = spendOnCokes(amount,machine);
When this is about to be executed, the environment consists of the variable called args which is the argument to the main method, and the variables input, machine, amount and cans. It doesn't contain the variable change because that isn't declared until after the method call.

The signature for the method spendOnCokes is:

public static int spendOnCokes(int sum,DrinksMachine mach)
which is followed by the code for the method. Note that signatures have types for each of the parameters, and a return type, method calls do not have types given with the arguments. That is because a method signature can be thought of as declaring new variables which form the initial environment for executing the code of the method. So the code for spendOnCokes starts executing with its environment consisting of the variables sum and mach. You can think of these as being assigned the values which match to them in the method call, it is as if:
sum=amount;
mach=machine;
were executed. Now you can see why, even though a method executes in its own environment, changes made to objects passed as parameters are carried on after the method finishes. Although mach and machine are separate variables in separate environments, because passing arguments to a method works like assignment, mach becomes an alias for the object referred to in the old environment by machine. The situation is something like:

The variable count is then added to the new environment and set to 0 when the declaration int count=0 is executed.

Executing a return statement in a method always has the effect of halting the method and returning to the point where the method was called (this is even if the return statement is inside a loop, for example). You can think of the return statement as assigning to a temporary variable the value that it returns, and then execution returns to the old environment, but the method call that was executed is replaced by this temporary variable. If we use the name return for the temporary variable, in our example it is as if return count in the method was return=count, and then the variable called return was made available in the old environment with the statement int cans = spendOnCokes(amount,machine) becoming int cans = return.

What are types for?

Every variable in Java has a type. You may think of a type as saying the sort of object a variable can refer to, but a better way of thinking of a type is as a description of what you can do with an object. If a variable is of type DrinksMachine then this says that what you can do with it is call on it any of the public methods (not including the constructors) of the class DrinksMachine, which we gave previously, or pass it as the nth argument to any method whose signature declares that its nth argument must be of type DrinksMachine (the only case of this we have seen so far is the method spendOnCokes whose second argument must be of type DrinksMachine), or you can assign it to another variable of type DrinksMachine.

You can also use a call to a method which has a particular return type to call a further method of that return type on, or as an argument to a method which requires that type. To demonstrate this, let us consider a static method whose return type is DrinksMachine:

  public static DrinksMachine cheaper(DrinksMachine m1,DrinksMachine m2)
  {
   if(m1.getPrice()<m2.getPrice())
      return m1;
   else
      return m2;
  } 
A complete program showing this method with a main method to demonstrate its use can be found in the file UseDrinksMachine3.java.

Suppose we have two variables, mach1 and mach2 of type DrinksMachine which have been set to refer to DrinksMachine objects. Suppose we want to get a Coke from whichever is the cheaper machine, and we have two pound coins. We could do that with:

   mach1.insert(100);
   mach2.insert(100);
   DrinksMachine mach3 = cheaper(mach1,mach2);
   Can myCan = mach3.pressCoke();
   int myChange = mach1.pressChange()+mach2.pressChange();
Here the assignment mach3 = cheaper(mach1,mach2) causes mach3 to become an alias for either mach1 or mach2, whichever has the cheapest price. So the code fragment represents putting a pound coin into both machines, then finding out which is the cheapest, pressing the "Coke" button on the cheapest, and pressing the "change" button on both machines.

We could replace the above statements by:

   mach1.insert(100);
   mach2.insert(100);
   Can c = cheaper(mach1,mach2).pressCoke();
   int myChange = mach1.pressChange()+mach2.pressChange();
calling pressCoke on whichever object is returned from the call to cheaper(mach1,mach2) without assigning a separate variable for it. Sometimes this is a useful shorthand to avoid declaring variables whose only use is to have a method applied on them once.

In this case, however, what we did was a bit odd. We would normally want to find the cheapest machine first, then put money in just that one and press its "Coke" button and "change" button. In this case, since we need to call three separate methods on the object returned by cheaper(mach1,mach2), we have to give it a separate name, so that the methods can be called on it in turn:

  DrinksMachine mach3 = cheaper(mach1,mach2);
  mach3.insert(100);
  Can myCan = mach3.pressCoke();
  int myChange() = mach3.pressChange();

Similarly, suppose we wanted to see how many cans of Coke we could get with a two pounds coin put into the cheapest machine. This can be done with the methods cheaper and spendOnCokes given above, as in:

  DrinksMachine mach3 = cheaper(mach1,mach2);
  int numCans = spendOnCokes(200,mach3);  
  System.out.println("I can buy "+numCans+" cans with two pounds");
but if we have no further need to refer to the cheapest machine, we could put the call to the method cheaper directly as the argument which requires its return type of the method spendOnCokes:
  int numCans = spendOnCokes(200,cheaper(mach1,mach2)); 
  System.out.println("I can buy "+numCans+" cans with two pounds");

So, as a general point, you can save on the declaration of a variable which is only used to hold the value of a method call and then to use that value just once by putting the method call directly in the place of that variable's use. This doesn't make any difference to how the program runs, it's just something that may make a program clearer to follow for people trying to read it and understand it. On the other hand, very complex expressions involving several method calls inside method calls may be hard to follow, and it may be helpful to break them into parts with variables used to hold the values of the parts.

Note that assignment statement must have a variable name on the left-hand side of the equals sign. It does not make sense, for example, to write:

  cheaper(mach1,mach2) = mach3;
it must always be:
  mach3 = cheaper(mach1,mach2);

So the important thing here is that types tell you how to fit the pieces of a program together: a non-static method call must be attached to a reference to an object of a type which can take that method call, an argument to a method call must be of a type which matches the parameter in the same position in the method signature. The situation is complicated by the way that one type in Java may be a sub-type of another type, but we won't cover that yet.

Scope of objects

As we said, a variable remains in existence until it goes out of scope, that is when execution goes past the closing } which matches the opening { of the block of statements it is called in. When a method is called, execution enters the environment of the method, so the variable is inaccessible, but it remains in existence with the value it was given and becomes accessible again when the method finishes. Note that even if the method that is called has an argument or an internal variable of the same name as a variable in the old environment, it is an entirely separate variable in the new environment, it has no connection with the variable of the same name in the old environment.

An object, however, remains in existence for as long as there is some variable which refers to it. So an object which is created inside a method call remains in existence after the method has finished executing so long as the method call causes a variable in the old environment to refer to it. The most common way in which this can happen is for a reference to the object created in the method to be returned as the result of the method. As an example, consider the following program in the file UseDrinksMachines4.java :

import java.util.Scanner;

class UseDrinksMachines4
{
 public static void main(String[] args)
 {
  Scanner input = new Scanner(System.in);
  System.out.print("Enter the price for drinks on your machine: ");
  int p = input.nextInt();
  DrinksMachine machine1 = new DrinksMachine(p);
  DrinksMachine machine2 = cheaperBy10p(machine1);
  System.out.println("The price you entered is "+p+"p");
  System.out.println("Machine 1 charges "+machine1.getPrice()+"p");
  System.out.println("Machine 2 charges "+machine2.getPrice()+"p");
 }

 public static DrinksMachine cheaperBy10p(DrinksMachine mach1)
 {
  int p = mach1.getPrice()-10;
  DrinksMachine mach2 = new DrinksMachine(p);
  return mach2;
 }

}
The idea of the method cheaperBy10p is that it takes a DrinksMachine object as its argument, and returns as its result a reference to a new DrinksMachine object which it creates whose price is 10p cheaper than the price of the DrinksMachine passed as its argument. The situation with the environments as the execution of the return statement in the execution of the call to the method cheaperBy10p takes place is:

The parameter mach1 in the method cheaperBy10p is matched against the argument machine1 in the call in the main method, so machine1 in the environment for the main method and mach1 in the environment for the cheaperBy10p call refer to the same object. A new DrinksMachine object is created in the cheaperBy10p call, with mach2 set to refer to it. A reference to this object is returned as the result of the call, and machine2 in the old environment was set to the result of the call, so it is set to refer to the object which mach2 refers to in the new environment. When the call to cheaperBy10p finishes with this return statement, the new environment disappears, and execution returns to the main method in the old environment. But the object created in the call remains in existence, because the variable machine2 refers to it.

The example above shows a case of the same variable name used in the old environment and the new environment. The variable called p in the new environment is a different variable from the one called p in the old environment. The code for cheaperBy10p sets up its own variable called p when it executes its statement int p = mach1.getPrice()-10. The variable called p in the old environment stays with its value which was set by int p = input.nextInt() in the main method. When execution returns to the main method after the execution of the call to the method cheaperBy10p, the variable name p means this original variable called p with the value set by the call input.nextInt(), it doesn't get changed by the use of a local variable of the same name in the method call.

In Java an object disappears if no variable refers to it. This could happen if all the variables that refer to it go out of scope, or if only one variable refers to the object and that variable is then set to refer to another object. For example, suppose an object is referred to by variable mach1 and by no other variable. Then suppose mach1=mach2 is executed. This causes mach1 to start referring to the variable which mach2 refers to, so nothing refers to the object it used to refer to, so that object disappears. In Java, the computer storage used for an object which is no longer referred to by any variable will be reused when new storage is required when a new object is created. This is referred to as automatic garbage collection. In some other programming languages it is necessary for the programmer to put in a special command to cause computer storage to be recycled in this way, otherwise after an object has been created its store may never be re-used while the program is executing even if no variable refers to it. In languages without automatic garbage collection a program that creates lots of new objects during its execution could cause the computer to run out of available store if no instructions are written to allow store to be recycled.


Matthew Huntbach

Last modified: 10 June 2005