Algorithms and Data Structures in an Object-Oriented Framework (“ADSOOF”)

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. It is likely you have seen the use of classes to define objects in previous modules, but using objects without knowing the code of their classes was a deliberate part of this module. The aim was to help you move away from thinking about a program as a whole thing where you need to know every bit of its code, to thinking about dealing with components of programs where you work on one bit without knowing the details of how other bits work.

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 structures 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<E> 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 instance methods

So far, we have used methods called on objects, but the only methods where we have seen the implementation have been declared as static in Java. If we have a method with the header(1):

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 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 name “instance method” is sometimes used for any method which is not static, as it is called on an “instance” of the class in which it occurs. We may also call them “object methods” or just “non-static methods”.

An instance 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 instance 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 instance method is made in the code of an instance 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 Java 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 class 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. In general, 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.

Defining and implementing a class

Good practice is to decide what you want a class for, and then to define the methods you want available for use on objects of that class so that they do what is necessary for those objects to fulfill their purpose. As mentioned above, this is its specification. Once you have done that, you can consider the variables which go inside each object of that class, and the code for the methods you have decided it needs. The variables which every object of a class has are sometimes called the fields of the class. The methods of a class will manipulate the variables of the object of that class it is called on. A method call can change what one of these variables refers to, either by making it refer to something else or by changing the object it refers to destructively if the variable is of a type which refers to a mutable object. As it is useful for objects to be immutable if that is a possibility, you may decide when considering the methods of a class not to provide any which change the values of the variables in the object, then you will make it an immutable class. The overall behaviour of the methods should make the objects of the class appear to behave in the way you want, so each method reflects some behaviour you want from the object.

This approach of deciding first what you want an object to do in terms of its interaction with other objects in a software system, and only after that deciding what goes inside a class to make it do that, is an important part of good quality programming. It means your software breaks down into well defined parts instead of being one big mess. This becomes more and more important as we move from small scale programming of the sort you might do as simple lab exercises to something on a more realistic scale.

The idea that the definition of a class in terms of how its objects interact with other objects is a separate thing from its implementation is what leads to it being considered good practice to make fields within a class private. Then no other software can treat the objects in terms of what are in them rather than in terms of how they interact. You could decide to change what is inside the class, maybe because you thought of a better way to get it to do what is wanted, without any code which uses that class having also to be changed so long as you made all its fields private. For the same reason, you should consider carefully whether any methods you add to a class make sense in terms of how it interacts with other classes. Sometimes, you need to define a method which is needed as an auxiliary method to help other methods, but does not make much sense on its own. You should declare a method like this as private, because it isn't intended for outside use, and if outside code could call it, it may mean objects of that class are used in a way that does not make sense. If you are given the definition of a class in terms of its public methods to implement, you should never add an extra public methods because you feel they might be useful. Adding unauthorised public methods could mean the application code which uses objects of that class stops making sense because it accesses internal data of those objects in some unauthorised way. That could lead to programs which are hard to understand, or even security problems.

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, and a link to enable you to download the .class file. The full code is now given below. You can also find it in the code folder (but note, it is slightly different there, 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 isntance method call being in the class where the instance 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 instance 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 (2) 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.

As noted previously although here null is returned by the method pressCoke to represent the machine being unable to supply a can of Coke, either because it has none left or because not enough money has been entered, this should not be considered good practice. It would be better to throw an exception, the code to do that is given below.

Separation of application from implementation

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.

If code which used the class DrinksMachine could refer directly to the arrayList objects inside a DrinksMachine object it would be harder to understand because there would be aspects of it which did not look directly as if they were about representing real drinks machines. It would mean if you decided to change the way you represent DrinksMachine objects, maybe having some other collection type inside instead of arrayLists, all the code which uses them would have to be checked to see if there was anywhere it referred directly to the internal arrayLists, and it would have to be changed. It could mean that if you perform an operation on a DrinksMachine object, maybe load it with 10 Coke cans, in one part of the program, you are surprised to see in another part it doesn't show the proper effect of that, because there is code somewhere which directly accesses the arrayLists of the DrinksMachine object and changes them in some way. Remember that because Java has aliasing, that might not be obvious because it may be that some reference to an arrayList<Can> object you use for some other purpose was set to alias the internal arrayList object of your DrinksMachine object.

In general, attempting to shortcut the clear separation between implementation and application code may sometimes seem an easy way around a tricky programming problem you have encountered. You may feel it is “powerful” to be able to access and change objects in ways which aren't part of their definition. However, once code becomes more than small-scale exercise code, this is likely to lead to software systems becoming confusing, hard to modify, and dangerous due to unexpected behaviour and possible security breaches. It is important that you get to understand and naturally use the idea of having the clear separation between application and implementation early on.

Methods which throw exceptions

We saw previously the use of a version of the DrinksMachine class which handled a machine which could not return a drink, either because it was empty or because not enough money had been inserted, by throwing an exception. In the code above, a test was made to see if enough money was inserted and enough cans were in the machine, but if not, the result null is returned. Just returning null means the programmer who uses the class DrinksMachine and its methods may not take this possibility into account, and that would cause problems later when a variable which is assumed set to refer to a Can object instead is set to null. Throwing a checked exception means the programmer who uses DrinksMachine is made to write code which deals with the possibility of no can being returned.

All that is required is for the code for the method pressCoke to be changed so that it throws the appropriate exception when necessary:

 public Can pressCoke() throws EmptyMachineException,NotEnoughMoneyException
 {
  if(balance<price)
     throw new NotEnoughMoneyException(""+(price-balance));
  else if(cokes.size()>0)
     {
      Can can = cokes.get(0);
      cokes.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     throw new EmptyMachineException("coke");
 }
The method pressFanta is similarly altered, with "fanta" in the place of "coke" in the statement throw new EmptyMachineException("coke"):
 public Can pressFantas() throws EmptyMachineException,NotEnoughMoneyException
 {
  if(balance<price)
     throw new NotEnoughMoneyException(""+(price-balance));
  else if(fantas.size()>0)
     {
      Can can = fantas.get(0);
      fantas.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     throw new EmptyMachineException("fanta");
 }
No other method has to be changed.

There are two different types of exceptions which can be thrown by the method, NotEnoughMoneyException which is thrown not enough money has been inserted to pay for a drink, and EmptyMachineException when the machine does not have any of the required drinks left. If there is not enough money inserted and there are none of the required drinks left, the code as it is written throws a NotEnoughMoneyException rather than an EmptyMachineException. Note that both types of exceptions which it can throw have to be named in the signature to the method following the keyword throws, this is necessary because they are checked exceptions. Both types of exceptions are created with a String argument which gives more detail on what went wrong. In the case of a NotEnoughMoneyException it gives the amount of money that still needs to be inserted to pay for a can, in the case of EmptyMachineException it gives the name of the drink which was asked for. This String value given by its constructor is what is returned by the getMessage method that can be called on an exception object. Note that as these are not exception types which are already provided by Java, but instead exception types we have defined in conjunction with our own type, we also have to write classes for them. This is easily done, it is a matter of extending the class Exception which is provided by Java:

class NotEnoughMoneyException extends Exception
{
 public NotEnoughMoneyException()
 {
  super();
 }

 public NotEnoughMoneyException(String message)
 {
  super(message);
 }
}
and
class EmptyMachineException extends Exception
{
 public EmptyMachineException()
 {
  super();
 }

 public EmptyMachineException(String message)
 {
  super(message);
 }
}
This uses the concept of inheritance, which we discuss
later. Strictly what we are doing is provided two constructors for each of the the new classes, one with 0 arguments, the other with one String argument, and both constructors just use the code of the equivalent constructor for Exception. But you can just think of this as the pattern for making your own exception type, since any exception class will be exactly the same apart from the actual name of the exception.


Footnotes

(1) We have already looked at generic methods, so if we take this into account, the standard form a static method header takes is:

static <V1,V2,...,Vm> R meth(T1 p1,T2 p2,...,Tn pn)
where V1,V2,...,Vm are any type variables used in the rest of the method. If there are no type variables, then this type variable declaration bit can be left out. In all the examples we saw where type variables were used, there was only one, so m was 1, and so there were no commas which are needed when there are more than one type variable declared. The return type R and the types of the parameters T1,T2,...,Tn can be one of the type variables, or a generic type which takes one of them as its type argument (or some more complex arrangement such as a generic type with more than one type argument, or one which takes another generic type which takes a type variable as its argument). Later we will look at generic classes where a type variable is declared with scope over a whole class, so it is declared in the class header and not the method header. This means that the header for an instance method takes the form:
<V1,V2,...,Vm> R meth(T1 p1,T2 p2,...,Tn pn)
where the return type or parameter types could also include type variables from the class header. For convenience, in this section of notes we ignore the possibility of methods having type variables.

Note also that method headers may also have “access modifiers” coming before what is given above, we have seen the most common examples public and private, and they may have a declaration of exception types that can be thrown coming after. If there are exception types declared, they are given by the keyword throws followed by the exception types which are separated by commas if there are more than one exception type.

(2) Actually, it is not quite true that the return statement is always the last thing to be executed in a method. We have seen Java's try-catch statement used to catch exceptions here. The try-catch statement covered there has an optional final part, the finally block. The code in the finally block is always executed no matter how the code in the try block is exited. This is so even if the code in the try block is exited through a return statement. It is possible to have a try block with no catch blocks, just a finally block. So if you want something to be executed in a method after the return statement, you can put the return statement in a try block and the code you want executed afterwards in a finally block. This would then enable you to write the pressChange method as:

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


Matthew Huntbach

Last modified: 28 February 2019