Defining objects in Java: return to drinks machines

Programmer defined objects in Java

So far we have used objects in Java but not seen how they are defined. We have used objects whose definitions are provided as part of the Java code library, and objects where we already have a .class file to define them.

When using Java we can talk about the application of a class of objects, which is code that uses object of that class, and the implementation of an object, which is the code that is used to make the objects work when methods are called on them. These two aspects are united with the specification which describes how an object of a particular class works in terms of its interaction with the application code. The writer of the application code writes that code under the assumption that objects of the class work according to the specification, he or she does not need to know what happens underneath to make them work that way. The writer of the implementation code has to make sure objects of the class work according to the specification, but he or she does not need to know what else happens in the application code.

Breaking up code into separate well-defined portions like this is a key part of good programming style. It may not seem so apparent on the sort of small-scale programs you write when you first start learning to program, but remember the sort of computer programs which are developed and sold commercially are on a hugely greater scale, written by whole teams of people. It is impossible for any one person to have a full understanding of how all the code works, breaking down the program into well-defined parts is the only way to manage its development.

The object-oriented style of programming also encourages re-use of code. This means that once a class of objects has been developed, that class can be used again in other circumstances where that sort of object is required. This means that it is not necessary to write every part of a program in terms of the most basic stuctures of the programming language. We have already seen software re-use when we have used code from Java's libraries. If we want something that works like an array, but whose size may change, for example, we do not start from scratch and think how we could write code to do that. Instead, we pick the class ArrayList from the Java library, and make use of it, it runs using the code already written by the developers of the Java system.

However, we cannot assume there will always be a suitable class already defined for every need we might have. It is important as Java programmers to be able to develop our own classes when necessary.

Static and object methods

So far, we have used object methods, but the only methods we have actually defined are declared as static in Java. If we have a method with the header
static R meth(T1 p1,T2 p2,...,Tn pn)
it means meth is the method's name, R is the method's return type, and there are n parameters where pi is the name of the ith parameter and Ti its declared type. Then a call takes the form meth(a1,a2,...,an) where each ai is an expression which evaluates to a value of type Ti. When the method call is evaluated, the code which forms the body of the method is evaluated in an environment which consist of variables p1, p2 up to pn where each pi is initialised to the value ai from the method call. Additional local variables may be added to the environment as variable declarations inside the code for the method are executed. The call may be used in any place where a value of type R is required. If the method call is executed as a statement on its own then the return value is ignored or there is no return value if R is void, in which case the method call will be intended to have an effect by changing a mutable object passed as an argument or by performing some external action.

Static methods are self-contained: all the data they use is passed to them as arguments (this ignores variables declared as static otherwise known as "class variables", but we will not consider these here). Methods which are not declared as static (so they are "object methods") must be "called on" a reference to an object of the class the method is defined in. In most cases this means the method call takes the form v.meth(a1,a2,...,an) where v is a variable name of that class, but the call could also be attached to another method call which returns a value of the type of that class. The method is defined in the class with the form

R meth(T1 p1,T2 p2,...,Tn)
{
 ... code ...
}
and execution of the code in the method works similarly to execution of the code in a static method, with an environment which contains variables p1, p2 up to pn with each pi initalised to the matching ai of the method call. However, the environment of the method call also contains the variables from the object it is called on. These are the variables which came into existence when the object was created and remain in existence until the object disappears when no other object references it. If these variables are changed to refer to something else during the execution of the code in the method, then that change is permanent, it remains in effect after the method call is finished. So that is how a method call on an object can change the state of that object. Like a static method call, an object method call where the return type of the method is R can be used in any place where a value of type R is required.

Note that if a call to a further object method is made in the code of an object method it does not have to be indicated as attached to any particular object. If it is not, it is taken to be attached to the same object that the method it is called in is attached to. If a method is intended to be called only by other methods in the same class then it can be declared as private which means any attempt to use it in another class will be indicated as an error by the Java compiler. This is done by putting the word private in front of the rest of the method. Using the word public in the same way means the method can be accessed in all other Jave classes. If a method is neither declared as public nor private it can be accessed by other classes, but only in the same package. But as we haven't discussed defining our own Java packages, you can ignore that possibility, and in general should declare methods as either public or private.

Object states

Object variables are declared inside a class but outside any methods. So if we have a type called MyObject and inside it (but not inside a method) declarations Type1 var1 and Type2 var2 it means that every object of type MyObject has its own variable called var1 which is of type Type1 and its own variable called var2 which is of type Type2.

If obj is a variable of type MyObject, then the variable var1 of the object referred to by obj can be referred to by obj.var1 and so on for other variables of the object. However, object variables are very often declared as private. As with private methods, this means they can only be referred to in the code of methods inside the class where they are declared. It is possible to declare a variable in an object as public meaning code in any other class can access it directly in the form obj.var1 where obj is an object reference and var1 a variable name. However, it is considered good practice not to have any public variables in a class. Making all object variables private means that an object controls its own state, the values of its variables can only be altered by its own methods.

A constructor of a class is written like a method in the class, but its name must be the same as the class name and it has no return type. So a constructor for objects of the type MyObject will have the form

 MyObject(T1 p1,T2 p2,...,Tn an)
 {
  ... code ...
 }
A call to it takes the form new MyObject(a1,a2,...,an) and it can be used wherever a value of type MyObject is required. So note the word new is always used when a new object is created, but not in any other circumstances. The code of the constructor is executed like a method, so in an environment with local variables p1 to pn where each variable pi is initialised to ai, with the addition of the variables of the object. This is when those variables come into existence and they remain in existence so long as the object constructed remains in existence. In most cases, a constructor will do nothing more than assign values to object variables. Note that any object variable which is not assigned a value in the constructor will be initialised to the value 0 if it is numerical, false if it is boolean, and null for any object type. Constructors can also be declared as either public or private, though at this stage you may wonder what use is a private constructor.

Example: the Drinks Machine class

All the above may seem rather abstract. It may be easier to grasp through an example. We have already seen examples of the use of objects of a class called DrinksMachine. At that point you were not shown the code that is inside the file DrinksMachine.java, you were only given the methods of the class. The full code is now given below (note this is slightly different from the code in the files, the reason why will be explained later):
import java.util.ArrayList;

class DrinksMachine
{
 private ArrayList<Can> cokes, fantas;
 private int price,balance,cash;

 public DrinksMachine(int p)
 {
  price = p;
  balance = 0;
  cash = 0;
  cokes = new ArrayList<Can>();
  fantas = new ArrayList<Can>();
 }

 public DrinksMachine(int p,int c, int f)
 {
  this(p);
  for(int i=0; i<c; i++)
     loadCoke(new Can("coke"));
  for(int i=0; i<f; i++)
     loadFanta(new Can("fanta"));
 }

 public void insert(int n)
 {
  balance=balance+n;
 }

 public int getBalance()
 {
  return balance;
 }

 public int collectCash()
 {
  int oldCash = cash;
  cash = 0;
  return oldCash;
 }

 public int getPrice()
 {
  return price;
 }

 public int pressChange()
 {
  int change=balance;
  balance=0;
  return change;
 }

 public Can pressCoke()
 {
  if(cokes.size()>0&&balance>=price)
     {
      Can can = cokes.get(0);
      cokes.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }

 public Can pressFanta()
 {
  if(fantas.size()>0&&balance>=price)
     {
      Can can = fantas.get(0);
      fantas.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }

 public void loadCoke(Can can)
 {
  cokes.add(can);
 }
 
 public void loadFanta(Can can)
 {
  fantas.add(can);
 }

 public boolean cokesEmpty()
 {
  return cokes.size()==0;
 }

 public boolean fantasEmpty()
 {
  return fantas.size()==0;
 }

 public void setPrice(int p)
 {
  price = p;
 }
}
Here the lines
 private ArrayList<Can> cokes, fantas;
 private int price,balance,cash;
indicate that an object of type DrinksMachine has five variables inside it, cokes and fantas of type ArrayList<Can> (that is, an arrayList of Can objects) and price, balance and cash of type int. So when any non-static method from class DrinksMachine is executed (and there aren't any static methods), it will be in an environment which has the parameters to the method as variables along with the variables cokes, fantas, price, balance and cash. In a constructor these will be new variables, in any other method they will be the variables from the object the method is called on. A variable with the name this is also provided in the environment for any non-static method, it is set to refer to the object the method is called on, or to the object being created in a constructor.

The lines

 public DrinksMachine(int p)
 {
  price = p;
  balance = 0;
  cash = 0;
  cokes = new ArrayList<Can>();
  fantas = new ArrayList<Can>();
 }
are a constructor for DrinksMachine. They mean that the call new DrinksMachine(expr) where expr is an expression which evaluates to an integer value returns a reference to a new DrinksMachine object, which has internal variables balance and cash set to 0, internal variables cokes and fantas set to an arrayList of Can objects of size 0, and internal variable price set to the value of expression expr. Note that the lines
  balance = 0;
  cash = 0;
are not strictly necessary, since if they weren't there these variables would be set to 0 as the default anyway.

The lines

 public DrinksMachine(int p,int c, int f)
 {
  this(p);
  for(int i=0; i<c; i++)
     loadCoke(new Can("coke"));
  for(int i=0; i<f; i++)
     loadFanta(new Can("fanta"));
 }
are a second constructor for class DrinksMachine. It is permissible to have more than one constructor so long as they differ in number and/or type of parameter. The two constructors mean that if a call new DrinksMachine(...) is made where ... is a single integer expression, the first constructor is used, if ... is three integer expressions separated by commas, the second constructor is used.

The first statement, this(p) in the code of the second constructor needs some explanation. It is another use of the word this than the one mentioned above. The word this as a method call as the first statement of a constructor means that the code from another constructor of the same class is used. In this case it means that the code for the first constructor with argument p is used to set the values of the internal variables of the new DrinksMachine object before the rest of the code in the constructor is used to change them. Here p is the variable which holds the value of the first argument to the constructor. Then the two loops cause methods loadCoke to be called c times and method loadFanta to be called f times where c and f are the variables in the local environment which hold the values of the second and third arguments to the constructor. Note these are examples of an object method call being in the class where the object method is defined. As they are not specified as being called on any other object they are called on the object being created by the constructor.

The methods in class DrinksMachine are short and their effect should be easily seen. The method written

 public void insert(int n)
 {
  balance=balance+n;
 }
causes the variable called balance of the DrinksMachine object it is called on to be increased by the value of its argument. So, for example, if d is a variable of type DrinksMachine and m is a variable of type int, the call d.insert(m) causes the statement balance=balance+n to be executed in an environment where m is a local variable into which the value of n has been copied, but balance means the actual variable called balance in the object referred to by d. So this variable gets increased by the value that is in m in the environment where d.insert(m) was called.

The method written

 public int getBalance()
 {
  return balance;
 }
does nothing but return the value of the balance variable of the DrinksMachine object it is called on. Strictly it means that when the call d.getBalance() is executed, the statement return balance is executed in the environment where balance is the balance variable of the object referred to by d.

The method written

 public int pressChange()
 {
  int change=balance;
  balance=0;
  return change;
 }
is an example of an object method with a local variable. When the call d.pressChange() is made, the local variable change comes into existence in the environment where this method is executed, it is set to the value of balance in this environment, and then balance in this environment is set to 0. So the result is that change holds the old value of the balance variable of the object referred to be d, while that variable has its value changed to 0, this change persisting after the method has finished. So this code enables the old value of balance to be returned and the value of balance to be changed in one method call. It is necessary to do it this way because a return statement is always the last statement to be executed in a method and causes the method to terminate. You could not, for example, write the method as:
 public int pressChange()
 // THIS IS DELIBERATELY SILLY CODE
 {
  return balance;
  balance=0;
 }
because executing return balance would cause the method call to terminate, and the following statement balance=0 would never get executed.

The method written

 public void loadCoke(Can can)
 {
  cokes.add(can);
 }
shows an example of a method call on an object causing a method to be called on an object referred to by one of its variables. An object of type DrinksMachine has its own variable of type ArrayList<Can> called cokes (and another of the same type called fantas). If c is an expression of type Can and d is a variable of type DrinksMachine then d.loadCoke(c) causes the call add(c) to be made on the ArrayList<Can> object referred to by the variable cokes of the DrinksMachine object referred to by d. The result is to add the object given by c to the end of one of the arrayList of Cans which forms part of the state of the DrinksMachine object. Note that "an expression of type Can" is not the same thing as a variable of type Can. A variable of type Can is one example of an expression of type Can, but another example is a method call which returns a reference to an object of type Can. So new Can("fanta") is an expression of type Can, as is d.pressCoke() where d is of type DrinksMachine. In the second constructor for DrinksMachine, the repeated call of loadCoke(new Can("coke")) means new Can objects are repeatedly created and added to the end of the arrayList referred to by the variables cokes in the object.

The method written

 public Can pressCoke()
 {
  if(cokes.size()>0&&balance>=price)
     {
      Can can = cokes.get(0);
      cokes.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }
shows a local variable, methods called on one of the arrayList objects which form part of the state of a DrinksMachine object, and the values of integer variables which form part of the state being changed. Remember it is meant to represent the operation of pressing the button labelled "Coke" on a drinks machine after some money has been inserted into the machine. The variable balance which forms part of the state of the object represents money inserted into the machine, but not yet spent or collected after pressing the "Change" button. The variable cash represents the amount of money spent on drinks in the machine since the cash was last collected (or since the machine was created if it has never been collected). The variable cokes refers to an arrayList which represents cans waiting to be delivered. In the code, cokes.remove(0) causes the first can to be removed from the stored cans. Then the price of a can is taken from the balance held and added to the cash held. A reference to the first can was taken before it was removed and held in the local variable can, this is then returned as the result of the method call.

Note the lines

   Can can = cokes.get(0);
   cokes.remove(0);
above could actually be written as
   Can can = cokes.remove(0);
This is because the remove method with int argument in class ArrayList has a return value, the value of the item removed when the method is called. Often we don't need to keep that value, so we call remove on an arrayList without making any use of it. In the code given the operation of getting the Can object at the start of the arrayList and then removing it from the arrayList is split into two parts to make it clearer.

The important thing about the methods in class DrinksMachine is that together they ensure a DrinksMachine object correctly represents a drinks machine. The only way the variables in a DrinksMachine object can be changed are in ways which reflect how a real machine would work. You cannot change the value of the variable cash in a DrinksMachine object except in a way which represents buying a drink or collecting all the accumulated money spent on the machine. The arrayLists which represent the cans stored in the machine can only be changed in a way which represents taking a can from one end and adding a can to the other.


Matthew Huntbach

Last modified: 15 February 2006