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

Java is an object-oriented language. This means that when a typical Java program executes, objects are created and interact with each other by calling methods. A typical Java program will consist of large numbers of files, each of which contains a Java class. A class defines a type of object in terms of what makes up the object and how it can interact with other objects.

In your first programming course, you came to think of a Java program as something that starts:

class SomeName extends basic {
 public static void main(String[] args) {
...
(the ... is just to indicate this is just part of the content of a file) where SomeName was the name you gave to the program. Following this, you had some Java code, and then you ended with:
...
 }
}
where the first } is to balance the { at the end of the line starting public and the second } balances the { at the end of the line starting class. Note that in Java it doesn't really matter how you space out your code with blank characters and new lines, it still means the same and behaves in the same way when compiled and run, however it is done. However, for other people to understand your code, it is important to lay it out in a way that makes its structure clear. Some people (myself included) prefer a layout where the opening { occurs on a new line of its own, and its balancing closing } occurs below it. It is very important, however, that you always think of { and } as forming a pair, when you have one should know where the other matching one is. A good way of ensuring this when you are writing programs in an editor is when you write { immediately put in the matching } and then fill in what comes in between.

In most Java programs the Java code which fills in the above is just a tiny part of the whole program. In your first programing course, you looked at defining separate static methods in the same class as the main method, which is one way of structuring programs by making what you thought of as separate "mini-programs". Before object-oriented programming became common, this was the main way of structuring programs. Now the main way is defining separate classes, this became popular as programs became longer and more complicated and additional ways of structuring were necessary to make sense of them, and to design, construct and modify them. It is always easier to deal with something complicated if you can divide it into self-contained parts and look at parts as separate things when necessary. The object-oriented approach to programming first became popular through the programming language C++ in the 1980s, although it originated in a programming language called Simula in the 1960s, and was carried on in the 1970s through a programming language called Smalltalk. Java was introduced in the 1990s as a programming language which resembled C++ but removed some of its complicated non-object-oriented aspects.

You will start to learn how to define separate classes in your object-oriented programming course. In this course, however, I want to start off by showing you how to use classes that have already been defined. This illustrates an important aspect of object-oriented programming: you can use code already written by someone else, you don't have to write every aspect of your program yourself. In fact, you can use other people's code without even knowing what it looks like, so long as you know what the result of using it will be. The drinks machine example is a simple case of this.

Using the Drinks Machine classes

In the directory
~mmh/DCS128/code/drinksmachines
you will find three files: Can.class, DrinksMachine.class and EmptyCanException.class which end in .class but do not have a matching file ending in .java. Actually, there are matching files called Can.java, DrinksMachine.java and EmptyCanException.java which I wrote and compiled to produce these .class files, but I have not made these available to you. You can use the code in these files without knowing what it is, so long as you know what the public methods are in these classes, and have the .class files. You will need to copy these three .class files into your own directory to use them.

To help you start out, there is also a file called UseDrinksMachines1.java in the directory ~mmh/DCS128/code/drinksmachines. Copy this, along with the three .class files, and compile it using the Linux command:

javac UseDrinksMachines1.java
to produce the file UseDrinksMachines1.class. Then entering the Linux command
java UseDrinksMachines1
will run the program.

Here is a complete listing of the code in the file UseDrinksMachines1.java:

class UseDrinksMachines1
{
 public static void main(String[] args)
 {
  DrinksMachine machine = new DrinksMachine(50,10,10);
  System.out.println("I insert 200p into the drinks machine");
  machine.insert(200);
  System.out.println("I press the button labelled \"Coke\"");
  Can myCan = machine.pressCoke();
  System.out.println("I press the button labelled \"change\"");
  int myChange = machine.pressChange();
  System.out.println("I have "+myCan+" and "+myChange+"p");
 }
}

The code in the main method represents putting a two pounds coin into a drinks machine, pressing the button labelled "Coke" and then pressing the button labelled "Change". The result is to return a can of coke and 150p in change. If you run the code, you will see statements printed saying this is what is happening. However, the System.out.println statements just print commentary, it is the other statements which do the work:

  DrinksMachine machine = new DrinksMachine(50,10,10);
  machine.insert(200);
  Can myCan = machine.pressCoke();
  int myChange = machine.pressChange();
If the code in your main method consisted of just the above, and you compiled and ran it, you would see nothing at all happen on the screen. But your computer will have done something which simulates putting money into a drinks machine, pressing buttons and getting results.

This illustrates an important aspect of computer programming - when you run a program, things happen that you can't see! In some ways, writing a computer program is like building a machine - you put things together to make bigger things, and you can make the machine you have put together run. But with a machine you have physical parts you can handle, and maybe a mechanism you can see working. With a computer program, it's all happening in the imagination. The only "real" thing that is happening, which you will have learnt about if you took a Computer Architecture course, is the transfer of tiny electrical charges, but you have to have the imagination to think of this in terms of what your program is meant to model. Being able to think through things happening when there is nothing there for you to handle or even to see is a crucial aspect of becoming a computer programmer.

Adding statements which print appropriate messages is one way of keeping track of what is happening. However, you should not confuse these statements with the statements that do the work. For example, here machine.insert(200) is the statement that has the effect of inserting an imaginary two pound coin into an imaginary drinks machine, while System.out.println("I insert 200p into the drinks machine") only has the effect of causing that particular message to be printed on the screen.

As a very minor change from what you saw in your first programming course, note the words extends basic no longer occur after the words public static void main(String[] args). These were there to enable you to access some simple input/output commands which are not already provided as a standard part of Java. As we won't carry on using those commands in this course, you won't see that extends basic in our examples.

Visualising drinks machine objects

The drinks machine example has been chosen deliberately to help you visualise what is happening in a simple computer program involving objects. It defines a class of objects which simulates real objects with simple and predictable behaviour we know about from the world we live and move in. If you are able to think about what is happening when methods are called on an object of class DrinksMachine in terms of a real drinks machine, which has buttons you can push, displays you can see, a slot you can put money onto, a slot you can take change from, and a place you can take a can of drink from, you are starting to use that imagination which is necessary to become a computer programmer. However, other things you may have to program may not have such a close correspondence to "real world" objects. We have to devise ways of thinking about them, perhaps involving diagrams or maybe just thought exercises, which make these abstract objects more real to us so that when we cause them to be manipulated by the computer programs we write we have a feel for what is happening, and can plan our programs to do useful work.

Most Java class files define a type of object. So the files Can.class, DrinksMachine.class and EmptyCanException.class contain the compiled version of code which enables the types Can, DrinksMachine and EmptyCanException to be used. You can use these types in just the same way you have used the built-in types which Java has, such as int and String. So you can use them to define variables of that type, or arguments to methods of that type. However, to use them, their .class files will have to be in the same Linux directory as the .class file of the code which uses them (this is a simplification of a more complex situation, but for the purpose of this course that is all you need to know). The type Can is used by objects of the type DrinksMachine, and the type EmptyCanException is used by objects of the type Can. At this point you need not be concerned at what the type EmptyCanException is used for, since it is needed for a more advanced aspect of Java we won't cover yet. However, you need to have the file EmptyCanException.class in your directory in order to be able to use the type Can.

In Java, you need to be careful to distinguish between a variable which refers to an object, and the object itself. A statement consisting of a type name followed by a variable name declares a variable of that name which has that type, you can also have a type name followed by several variable names separated by commas which declare several variables of that type. What this means is that that the names defined may be used to refer to objects of that type, but it does not cause an object of that type to be created. So:

  DrinksMachine machine;
means a variable called machine has been set up and may be made to refer to objects of type DrinksMachine, but it doesn't cause any DrinksMachine object to be created.

An expression consisting of the word new followed by the type name followed by some arguments within round brackets (it may have zero arguments, but in that case the brackets must still be there) creates a new object of that type. In order for that object to be used, a variable must be set to refer to it. This can be done by an assignment statement, so for example

  machine = new DrinksMachine(50,10,10);
creates a new DrinksMachine object and causes the variable machine to refer to it. It could also be done (we shall see an example later) by making the new object expression an argument to a method. The number of arguments between the round brackets and their types depend on how the type has been defined, below it is explained that the expression here simulates the introduction of a new drinks machine which charges 50p for its drinks and initially holds 10 cans of Coke and 10 cans of Fanta.

In Java it is permissible, and common practice, to combine the declaration of a variable with an assignment giving it a value. So the statement

  DrinksMachine machine = new DrinksMachine(50,10,10);
is a combination of the above two statements. Please remember, however that a type name followed by a variable name is only used when you want to declare a new variable; when you use the variable name after that you should not accompany it with the type name. Note also that variable names have a scope - they can be used only up to the closing } which matches the nearest opening { which occurred before the variable declaration.

You have seen static methods in Java, which you have learnt to think of as "mini-programs", but most methods in Java classes are not static. A method which is not static must be called attached to an object reference, we say it is "called on" that object. The methods that may be called on a particular type of object are specified in the class of that object. The statement

  machine.insert(200); 
calls the method insert with the argument 200 on the object referred to by the variable machine. Since the method call is a statement on its own, it is likely to be a method which has return type void, and calling it has some sort of effect which carries on after the method call is finished. In this case, it represents inserting a two pounds coin (or 200p) into the drinks machine, although the drinks machine does not return anything it is changed internally and now stores the fact that this amount of money has been put into it.

The statement

  Can myCan = machine.pressCoke();
is another combination of a variable declaration and an assignment which gives that variable a value. It is the equivalent of the two separate statements (a variable declaration followed by an assignment):
  Can myCan;
  myCan = machine.pressCoke();
A variable called myCan is declared of type Can, that is it can refer to objects of type Can. This type is defined in the file Can.java which was compiled to get the file Can.class. This time, we do not directly create a new Can object, but obtain one by calling the method pressCoke on the object referred to by the variable machine. The method pressCoke has zero arguments, but note it is still necessary to include the opening and closing brackets in the call even though there are no arguments. The return type of the method pressCoke must be Can. Calling this method represents pressing the button marked "Coke" on the drinks machine. When this operation takes place, a can of Coke is delivered, but also the internal state of the machine changes - it has one less can of Coke stored internally, and also the amount of money it registers as holding for you is reduced by the price of a can of Coke.

The statement

  int myChange = machine.pressChange();
is similar to the previous statement, this time declaring a variable of type int which is initialised to the value returned from calling the zero-argument method pressChange on the object referred to by the variable machine. This represents pressing a button which causes the machine to return the amount of money that has been inserted but has not been spent on buying cans of drink.

Coding with DrinksMachine objects

Now we have introduced the concept of objects of type DrinksMachine, and seen a very simple example of their use, let us consider a full definition of their use. Here is a list of all the public methods, given by their signatures, in the DrinksMachine class:
class DrinksMachine
{
  DrinksMachine(int p)
  DrinksMachine(int p,int c, int f)
  void insert(int n)
  int getBalance()
  int getPrice()
  boolean cokesEmpty()
  boolean fantasEmpty()
  int pressChange()
  Can pressCoke()
  Can pressFanta()
  void loadCoke(Can can)
  void loadFanta(Can can)
  void setPrice(int p)
  int collectCash()
}
The full file DrinksMachine.java would contain variable declarations which represent the internal state of the machine, and code for each of the methods which is obeyed when they are executed. However, these method signatures are all you need know to be able to make use of DrinksMachine objects. Just like a real machine, you don't need to know what is inside and how it works to use it, all you need to know is how to interact with it using the interface it presents to the public, through its displays and buttons and slots. To fully understand, you need an explanation of what each method will do. The names of the methods and the types of their arguments and return type will generally give a clue.

The first two methods in the class are its constructors, although constructors are often considered to be a separate sort of thing from methods. Calling a constructor always causes an entirely new object of its class to be created. A constructor has the same name as the class, and does not have a return type. All other methods must have a return type, even if it is void which is used for the return type of methods that don't return anything. As we saw above, a constructor is called by using the word new followed by the class name and the arguments. The word new is only used for calling constructors in Java (and for creating new arrays, which could be considered a special form of constructor), it is not used when any other method is called, and because it has this special use you may not use the word new as the name of a variable or a method in a Java class.

Java allows a method to be overloaded, which means the same name may occur more than once in the methods of a class, so long as each time it is used it has an argument list which differs from other argument lists of the same named method, either by having a different number of arguments or by having different types of arguments. In the DrinksMachine example, only the constructor is overloaded. There is one constructor with a single int argument, and another constructor with three int arguments. So to create a new DrinksMachine, you must either call new DrinksMachine(n) or new DrinksMachine(m,n,p) where m and n and p are expressions which evaluate to a value of type int. This means they could be variables of type int, or integer literals, that is actual numbers, or arithmetic expressions, or calls to methods which return values of type int. The idea is that a call new DrinksMachine(m,n,p) represents setting up a new drinks machine where a drink costs m and it is preloaded with n cans of Coke and p cans of Fanta. The call new DrinksMachine(n) represents setting up a new drinks machine which isn't preloaded with any cans. Note that the simple drinks machine we are modelling is one which is set up so that it only has buttons to dispense two varieties of drinks, Coke and Fanta.

As we saw above, the method insert with an argument of type int represents inserting a coin into the drinks machine, where the arguments gives the value of the coin in pence. If you want to know how much money you have inserted so far and not used, you call the method getBalance on the object representing the drinks machine. You can think of this as looking at a display showing the amount inserted on a real machine. Calling this method returns a value of type int. It is very important to understand the difference between a method which returns a value, and a method which displays a value in some way - they are not the same thing at all! When a method returns a value, it is something that is entirely internal to the program's execution. It only makes sense if you do something with the value returned, you could pass it as an argument to another method which maybe displays it, as in:

  System.out.println("I have "+machine.getBalance()+"p left");
or which uses it in some other way, or you could assign it to a variable for use elsewhere later. It never makes sense, however, to call a method which does nothing but return a value as a statement on its own, as in:
  machine.getBalance();
The effect is to return the balance but do nothing with it, so nothing has been achieved. Don't assume, for example, that the Java execution mechanism will magically know you want the value to be printed. The Java execution mechanism will do just what it is meant to do and nothing else. Computers are not like human beings. When we human beings communicate with each other, if one person doesn't express something quite correctly, the other person will often try to use common sense to work out what was really meant. Computers don't have common sense like that.

The method getPrice is similar to the method getBalance, except that it returns the price of a can of drink rather than the current amount inserted. The drinks machines we are modelling in this class are of a simple design where there is just one price for all drinks, we would need a different set of methods if we wanted to model machines where different drinks had different prices. You can think of calling this method as equivalent to looking at a display of the price on a real machine. Two further methods, cokesEmpty and fantasEmpty also represent looking at displays on the machine, in this case the signs that indicate that a particular drink has run out. These return boolean values, rather than integers. There is nothing in the public interface of a DrinksMachine object which tells us how many cans of a particular drink it has unless it has none, even though that figure may be represented internally in the object. This is the same as a real drinks machine, where as you can't look inside and there is just a display which lights up when one variety of drink runs out, you can only tell it either has none or some of that drink.

Note that none of the methods of class DrinksMachine actually causes anything to be displayed. They return values which could be passed to System.out.println or to other methods which could cause them to be displayed. This is good practice because it is flexible. You should always make the code which causes things to be displayed in the screen as separate as possible from the code which represents things internally in the program. The code which displays things is known as the user interface code (do not confuse this use of the word "interface" with the use to mean the public methods of a class which form its interface with the rest of a program). A mark of a good computer programmer is the ability to separate things out, so that if you wish to change one thing you know clearly where that thing is and don't have to make changes throughout the program. For simplicity in this course, our user interfaces will be entirely text based. However, we know that modern computer programs tend to have graphical user interfaces. If our DrinksMachine class has no user interface code, then we can switch from using it with a text-based user interface to using it with a graphical user interface easily, it's just a matter of passing the values returned by methods such as getBalance, getPrice, cokesEmpty and fantasEmpty to other methods which give a graphical display rather than a text display.

The method pressChange doesn't just return a value, it also changes the internal state of the object it is called on. It returns the same value that a call of getBalance on the same object would give, but if another call of getBalance is then made, it would return 0. You could test this by running a program whose main method had the statements:

  DrinksMachine machine = new DrinksMachine(50,10,10);
  System.out.println("I insert 200p into the drinks machine");
  machine.insert(200);
  System.out.println("The balance display reads "+machine.getBalance());
  System.out.println("I press the change button");
  machine.pressChange();
  System.out.println("The balance display reads "+machine.getBalance());
Note that in this case, even though the method pressChange does have a return value, it can make sense to call it in a statement on its own and not do anything with the return value, if it happens we want the effect of changing the state of the object it is called on but don't need to use the value that is returned. This is what is done in the line
  machine.pressChange();
a value is returned by this call, but nothing is done with it. Note that the statement
System.out.println("The balance display reads "+machine.getBalance());
occurs twice, but has a different effect when the second occurrence is executed than when the first occurrence is executed, demonstrating that the object referred to by the variable machine has been changed. Also note that in this statement, the result of calling the method getBalance on the object referred to by the variable machine isn't put into an int variable, but is joined directly to a string and the result passed to a System.out.println to be printed. In Java, if you join anything to a string using +, you get a new string created by joining the original string to the string equivalent of the thing.

The methods pressCoke and pressFanta have the effect of returning a Can object, and also of changing the state of the machine the method is called on to remove the can object returned from its internal state, and also of changing the value returned by a call of getBalance to take off the price of a can of drink. They represent pressing a button labelled "Coke" or "Fanta" to deliver the appropriate drink. At this point you do not need to know what you can do with a Can object, except that its string equivalent will be either "can of Coke" or "can of Fanta" as appropriate.

But what happens if you call pressCoke or pressFanta when there are no drinks of the appropriate sort left in the machine? (Of course, we really mean imaginary drinks in our imaginary machine, whose only real existence is as data stored in the computer!). One way of dealing with this might be to "throw an exception", which is an aspect of Java you will cover later. In this case, however, the class DrinksMachine has been programmed so that if a DrinksMachine object represents a drinks machine with no Coke cans left, a call of pressCoke will return null, and similarly for the case where no Fanta cans are left. The methods pressCoke and pressFanta have also been programmed so that if they return null the value returned by a call of getBalance will not change, representing the requirement that if you are not served a drink you should not be charged for one.

Any variable which may hold any type of object may also hold the special value null, and any method which returns any type of object may also return null. The value null can be thought of as meaning "unset". You can test if a variable called obj is set to null by the test obj==null. If when code is being executed an attempt is made to call a method on a variable which has been set to null, then an exception of type NullPointerException will be thrown.

The remaining methods in class DrinkMachine represent operations which the owner of a drinks machine might carry out. To keep things simple, no distinction has been made between these and the operations a customer would carry out on the machine, they are all represented as public methods. The methods loadCoke and loadFanta represent filling up the machine with new cans. They take a Can object as their argument. So to call them, you would need to know how to create a new Can object. The class Can has a constructor which takes a string and produces a Can object representing the drink named by that string. So new Can("Coke") produces a new Can object representing a can of Coke. Given this, the following code fragment represents loading ten cans of Coke into the machine referred to by variable machine

  for(int i=0; i<10; i++)
     machine.loadCoke(new Can("Coke"));
This is an example of creating an object and immediately passing it as an argument rather than assigning a variable to refer to it. Each time new Can("Coke") is called, a new Can object is created, so ten separate Can objects will be created by the code above. Note, there is no check that cans of the appropriate variety are loaded. So, for example, the following code fragment:
   for(int i=0; i<10; i++)
     machine.loadCoke(new Can("Fanta"));
is permissible, and represents loading cans of Fanta into the Coke can dispenser. You could experiment with this by trying the following main method:
 public static void main(String[] args)
 {
  DrinksMachine machine = new DrinksMachine(50);
  for(int i=0; i<10; i++)
     machine.loadCoke(new Can("Fanta"));
  System.out.println("I insert 200p into the drinks machine");
  machine.insert(200);
  System.out.println("I press the button labelled \"Coke\"");
  Can myCan = machine.pressCoke();
  System.out.println("I press the button labelled \"change\"");
  int myChange = machine.pressChange();
  System.out.println("I have a "+myCan+" and "+myChange+"p");
 }
and you will see the code is such that a drinks machine loaded with Fanta cans into the Coke dispenser will indeed deliver cans of Fanta when the "Coke" button is pressed.

The method setPrice takes an argument of type int representing a new price for drinks given in pence. After this method has been called on a DrinksMachine object the price charged for a drink changes to the new price that was given. Again, you could write some code to experiment with this and show it working. The final method, collectCash represents taking out the money that has been collected as drinks have been sold. It returns a value of type int, representing the amount of money the machine has accumulated. When a call of collectCash is made on a DrinksMachine object, this value is returned, but also the internal state of the object is changed to represent the amount of money stored in the machine going down to 0.

Separation of concerns: definition and use of a class

We have shown that a class can be used by a programmer without the programmer knowing what is in the class. First of all, however, let's distinguish between a class and an object. You can think of the class itself as the design or "blueprint" for objects, and objects as machines built according to that design. So the actual Java class DrinksMachine represents plans for building a drinks machine and saying how it operates, while creating a new object of type DrinksMachine using a constructor from the class represents building an actual machine of that design.

Objects interact through calls on the public methods defined in their classes. We don't need to know what actually happens inside the objects when methods are called on them, but we do need to be sure of what the results will be. In the drinks machine example, we had an intuitive feel for what objects of the class DrinksMachine should do, because they were modeling something familiar from the real world. We described the behaviour of these objects informally using English, but if we wanted to be a bit more precise about it, we could use some logical notation which says exactly what effect a call of a particular method should have. That is the way classes ought to be written - we start off first with a description or specification of how objects of that class should behave in terms of what happens when various methods are called on them, then we write the code for the class so that it works that way. Or we might write the specification for the class, and then give it to another programmer to write the code for the class. That is a good division of work. We don't need to worry about what is inside the class so long as it works to its specification, the programmer writing the class doesn't need to worry about what the programs that use it do, so long as it works to its specification.


Matthew Huntbach

Last modified: 17 January 2006