ECS510 Algorithms and Data Structures in an Object-Oriented Framework (“ADSOOF”)
Exceptions in Java: drinks machines implemented with exceptions
The code you have been given for the class DrinksMachine
simulates pressing the Coke or Fanta button when there are no drinks
of that type available by returning null
from the call
pressCoke()
or pressFanta()
on the
DrinksMachine
object, rather than a reference to a
Can
object. As noted
this is actually considered bad programming practice, and a better simulation
would work by throwing an exception.
To show the use of exceptions, there is a modified version of the code for
drinks machines objects in the file DrinksMachineA.class
.
In order to use this, you will also need the file for the class of
exceptions it can throw, which is in the file
EmptyMachineException.class
. Modified versions of the code
in the previous notes which demonstrate use of objects is given in the
Java files
UseDrinksMachines1A.java
,
UseDrinksMachines2A.java
,
UseDrinksMachines3A.java
and
UseDrinksMachines4A.java
.
The only difference between the code for DrinksMachineA
and the
code for DrinksMachine
is that the two methods
pressCoke
and pressFanta
are declared as possibly
throwing the exception EmptyMachineException
. This is indicated
by the headers for these methods in the file DrinksMachineA.java
,
which you haven't seen and don't need to see, being:
Can pressCoke() throws EmptyMachineException Can pressFanta() throws EmptyMachineExceptionAlthough you don't need to know what the code inside these methods is, you do need to know these are their headers in order to use them properly. The reason for this is that the
throws EmptyMachineException
indicates these
methods may throw a checked exception of the type
EmptyMachineException
. A checked exception
is an exception which has to be dealt with in the code which calls
the method which may throw it. The other sort of exception is a
run time exception where you don't have to write special code
to deal with it. In general, the only exceptions which are run time
exceptions are those which can occur in almost any piece of code.
An example of a run time exception is NullPointerException
,
which as we saw previously,
could be thrown by any call of a method, since it is thrown by any call of a
method on a variable when that variable is set to null
rather
than to an object reference. An exception which represents an exceptional
occurence in the world being handled by the program, rather than an
internal programming error, should always be a checked exception not
a run-time exception.
Note, below, it is
explained why DrinksMachineA
needs further modifications,
but for most of these notes we shall ignore the additional issue raised there.
If a method has a header which shows that it throws a checked exception,
then any code in which that method is called must be written to
deal with the possibility of that exception. Java's
try
...catch
statement is one
way of doing that. The other way is that the method in which
the call which may throw the exception occurs is itself declared as
throwing the exception, we shall discuss that
later.
A try
...catch
statement consists of the keyword try
, followed by
{
, followed by some statements, followed by
catch
, followed by (
an exception
type, a variable name and )
followed by {
followed by some statements (“some” here could be 0 statements),
followed by }
. When it is executed, the statements in
the try
part are executed one by one as normal Java
statements, but if when executing one of them an exception is thrown,
then execution of the try
parts stops. If the
exception is of the type of the exception type given in the
catch
part (or a subtype of it - we will discuss
subtypes when we discuss
inheritance),
then the statements in the catch
part are executed.
If either all the statement in the try
part are executed
and no exception occurred, or an exception occurred which was
of the type given in the catch
part, causing the
try
part to be abandoned and the catch
part
to be executed, then execution continues to the statement following
the try
...catch
statement, or the method it
is in ends normally if it is the last statement. This is described as
the exception being “caught”, the whole program then just carries on as
normal. It is also possible for a try
...catch
statement to have more than one catch
parts, and (although it
is rare) to have an extra part indicated by the key word
finally
. See
here
for a full explanation.
This exception catching mechanism is perhaps better understood by looking
at some code which uses try
...catch
statements.
Here is the version of the previous
UseDrinksMachines1.java
which uses the modified DrinksMachineA
:
class UseDrinksMachines1A { public static void main(String[] args) { DrinksMachineA machine = new DrinksMachineA(50,0,10); System.out.println("I insert 200p into the drinks machine"); machine.insert(200); System.out.println("I press the button labelled \"Coke\""); try { Can myCan; 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"); } catch(EmptyMachineException e) { System.out.println("The machine is empty"); System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have "+myChange+"p"); } System.out.println("I have finished"); } }Since a call to
pressCoke
may cause an
EmptyMachineException
to be thrown, it must
occur within a setting where that exception is handled. So here
it is in a try
part which has a catch
part which handles it. What happens here is that when
myCan = machine.pressCoke();is executed, if it causes an
EmptyMachineException
to
be thrown, the remaining statements within the try
part are
not executed, instead the statements within the catch
part are executed. If the pressCoke
method executes
normally, then the statements following it in the try
part are executed, and the statements in the catch
part
are not executed. In either case, the final statement which causes
I have finished
to be printed is executed and the whole
program finishes with no special error message. You can see that
the drinks machine set up in this program is set up with no Cokes in
the machine, so the exception will be thrown. Change the line
DrinksMachineA machine = new DrinksMachineA(50,0,10);by having a positive number instead of
0
to see the
try
...catch
statement executed without
an exception being thrown.
A more complex use of exceptions is shown in a version of the previous
UseDrinksMachines2.java
which uses the modified DrinksMachineA
:
import java.util.Scanner; class UseDrinksMachines2A { public static void main(String[] args) { Scanner input = new Scanner(System.in); DrinksMachineA machine = new DrinksMachineA(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,DrinksMachineA mach) { int count=0; mach.insert(sum); try { while(mach.getBalance()>=mach.getPrice()) { mach.pressCoke(); count++; } } catch(EmptyMachineException e) { } return count; } }Here the
try
...catch
statement is
inside the method spendOnCokes
. The loop
inside the try
part could end when the test
mach.getBalance()>=mach.getPrice()
returns
false
, meaning the money left as balance in the
machine is less than the price of a can so no more Cokes can be
obtained. The catch
part is then never reached, the
next thing is to return the value of count
and
the method call terminates. However, if mach.pressCoke()
is called when the machine referred to by mach
has
run out of Cokes, an EmptyMachineException
is thrown,
this causes the try
part to be terminated, even though
the exception is thrown inside a loop the loop is exited, and the
code in the catch
part is executed. But here there is no
code in the catch
part, it is empty, which is permitted in
Java. So the method is terminated with the value of count
returned. Here the exception is designed to be an alternative way
of exiting the loop, in the case when we are finishing because the
machine has run out of Coke cans rather than we have run out of money.
As another example, the following shows a case of a
try
...catch
statement inside a
try
...catch
statement:
import java.util.Scanner; class UseDrinksMachines3A { public static void main(String[] args) { int p; Scanner input = new Scanner(System.in); System.out.print("Enter the price for drinks on machine 1: "); p = input.nextInt(); DrinksMachineA mach1 = new DrinksMachineA(p,0,10); System.out.print("Enter the price for drinks on machine 2: "); p = input.nextInt(); DrinksMachineA mach2 = new DrinksMachineA(p,10,10); mach1.insert(100); mach2.insert(100); DrinksMachineA mach3 = cheaper(mach1,mach2); try { Can myCan = mach3.pressCoke(); System.out.println("I bought a can of Coke from the cheaper machine"); } catch(EmptyMachineException e1) { System.out.println("The cheaper machine has no cans of Coke left"); if(mach3==mach2) mach3=mach1; else mach3=mach2; try { Can myCan = mach3.pressCoke(); System.out.println("So I bought one from the other machine"); } catch(EmptyMachineException e2) { System.out.println("Neither has the other machine"); } } int myChange = mach1.pressChange()+mach2.pressChange(); System.out.println("I am left with "+myChange+"p from two pounds"); } public static DrinksMachineA cheaper(DrinksMachineA m1,DrinksMachineA m2) { if(m1.getPrice()<m2.getPrice()) return m1; else return m2; } }The idea is that we try to buy a Coke from the cheaper machine, but if it is empty of Cokes, shown by an
EmptyMachineException
being thrown, we try to buy from the other machine - which may be empty
of Cokes as well. As an interesting side point here, we don't know
whether the cheaper machine, referred to by the variable mach3
,
refers to the machine referred to by mach1
or the machine
referred to by mach2
. So to switch it to refer to the
other one, we use:
if(mach3==mach2) mach3=mach1; else mach3=mach2;The test
mach3==mach2
returns true
if mach3
and mach2
are aliases, that is, they
refer to the same object. Otherwise it returns false
,
even if mach3
and mach2
refer to separate
objects which are otherwise identical (same price, same number of drinks,
same balance, same amount of accumulated cash). You are sometimes warned not
to use ==
when testing objects for equality because it may
not have the effect you expect, but it is correct to use it when you
really do want to test that two object references actually refer to the
same object.
Note, it is not essential to program this scenario in this way. We
still have the method cokesEmpty
which tests whether
a machine is empty of Cokes, we could use that rather than by
calling pressCoke()
and seeing if an
EmptyMachineException
is thrown. In fact, the
following code is probably clearer:
import java.util.Scanner; class UseDrinksMachines5A { public static void main(String[] args) { int p; Scanner input = new Scanner(System.in); System.out.print("Enter the price for drinks on machine 1: "); p = input.nextInt(); DrinksMachineA mach1 = new DrinksMachineA(p,0,10); System.out.print("Enter the price for drinks on machine 2: "); p = input.nextInt(); DrinksMachineA mach2 = new DrinksMachineA(p,10,10); mach1.insert(100); mach2.insert(100); DrinksMachineA mach3 = cheaper(mach1,mach2); try { if(!mach3.cokesEmpty()) { Can myCan = mach3.pressCoke(); System.out.println("I bought a can of Coke from the cheaper machine"); } else { System.out.println("The cheaper machine has no cans of Coke left"); if(mach3==mach2) mach3=mach1; else mach3=mach2; if(!mach3.cokesEmpty()) { Can myCan = mach3.pressCoke(); System.out.println("So I bought one from the other machine"); } else System.out.println("Neither has the other machine"); } } catch(EmptyMachineException e) { } int myChange = mach1.pressChange()+mach2.pressChange(); System.out.println("I am left with "+myChange+"p from two pounds"); } public static DrinksMachineA cheaper(DrinksMachineA m1,DrinksMachineA m2) { if(m1.getPrice()<m2.getPrice()) return m1; else return m2; } }Here, the method
pressCoke
is only called when we have
already tested that the machine has Cokes available, so we know an
EmptyMachineException
can never be thrown. However, we
still have to write code which would catch it if it was thrown, since
every time we call the method pressCoke
it must be with
code to catch the EmptyMachineException
that method could
throw, regardless of whether we know it is called in circumstances where
that could never happen.
You may wonder why the Java try
...catch
statement not only names the type of exception caught, but has an
additional variable name after that. In most cases that variable
name is e
, but when we had a
try
...catch
inside a
try
...catch
statement in
UseDrinksMachines3A.java
we used the names e1
and e2
for the two
separate exceptions. In fact any variable name could be used, but the convention is
that e
is used.
What is actually happening here is that an exception is itself an object.
The catch
part of a try
...catch
statement sets its variable to refer to the exception object caught.
In most cases the exception object is not otherwise referred to,
but one way it can be referred to is to call the zero-argument method
getMessage
on it. When exception objects are created, they
may be given a message which says more about the circumstances that caused
them. This message is in the form of a String
and calling
getMessage()
on the exception object returns it.
The code for DrinksMachineA
has been written so that
if an EmptyMachineException
is thrown from a call to
the pressCokes
method, the message of that object is
"coke"
, and if it is thrown from a call to
the pressFantas
method, the message is "fanta"
.
The following program makes use of that:
class UseDrinksMachines6A { public static void main(String[] args) { DrinksMachineA machine = new DrinksMachineA(50,10,0); System.out.println("I insert 200p into the drinks machine"); machine.insert(200); Can myCan1=null,myCan2=null; try { System.out.println("I press the button labelled \"Coke\""); myCan1 = machine.pressCoke(); System.out.println("I press the button labelled \"Fanta\""); myCan2 = machine.pressFanta(); } catch(EmptyMachineException e) { System.out.println("The machine is empty of "+e.getMessage()+"s"); } System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have "+myCan1+" and "+myCan2+" and "+myChange+"p"); } }The variable
e
in the catch
part gets set to refer to the
EmptyMachineException
object that us thrown in the try
part if
execution of that part does throw an exception.
Note that the EmptyMachineException
which e
could
get set to could have been thrown by either the call
myCan1 = machine.pressCoke()
or the call
myCan2 = machine.pressFanta()
in the
try
part. The String
returned by the call of
e.getMessage()
in the catch
part says which it is.
The program in
UseDrinksMachines6A.java
has some faults, however. If the machine is empty of Cokes (as the
program is written, it will be), the exception caused by
machine.pressCoke()
means the call
machine.pressFanta()
will never be made, because
execution goes to the catch
part before it is reached.
Another point is that we have to declare
Can myCan1=null,myCan2=null;that is, initialising the two variables
myCan1
and
myCan2
to null
. The reason for this is
that the Java compiler issues an error if a local variable to a method
may never get assigned a value. If
myCan1 = machine.pressCoke()
causes an
EmptyMachineException
to be thrown, neither variable
gets given a value, the exception means the assignment to
myCan1
is not completed, and the later statement
myCan2 = machine.pressFanta()
is never
reached. If myCan2 = machine.pressFanta()
throws the exception, then while myCan1
is given a
value, myCan2
is not. Initialising these variable to
null
means they will always have a value, even if that
value is null
. Note also that declaring the variables
myCan1
and myCan2
before the
try
...catch
statement means we can
access them in the code after it. The statements within the
{
and }
of the try
and
catch
parts are normal blocks of code, so variables
declared within them (as in the previous examples) have local scope only
within those blocks, it is not even possible for a variable declared
in the try
part to be accessed in the catch
part.
If a method that may throw a checked exception is not called inside
the try
part of a try
...catch
statement with a catch
parts which catches it, then the
method in which the method call occurs must be annotated as throwing
the exception. So what happens when an exception is thrown is that it
may be inside several layers of method calls, and it gets thrown
up each layer until it is caught. As a simple example, consider
the following program:
import java.util.Scanner; class UseDrinksMachines7A { public static void main(String[] args) { Scanner input = new Scanner(System.in); DrinksMachineA machine = new DrinksMachineA(50,10,10); System.out.print("Enter the sum of money you wish to spend on cokes: "); int amount = input.nextInt(); System.out.print("Enter the number of cokes you wish to buy: "); int number = input.nextInt(); try { buyCokes(number,amount,machine); System.out.println("You successfully bought "+number+" cokes"); } catch(EmptyMachineException e) { System.out.println("The machine ran out of cokes"); } int change = machine.pressChange(); System.out.println("You have "+change+"p left"); } public static void buyCokes(int num,int sum,DrinksMachineA mach) throws EmptyMachineException { mach.insert(sum); for(int i=0; i<num; i++) mach.pressCoke(); } }The method
buyCokes
here is intended to model
putting some money into a machine and buying a fixed number of Cokes
from that machine. It calls the method pressCoke()
on
the machine, but not within a try
...catch
statement which catches the EmptyMachineException
this method may throw. Because of this, the method
buyCokes
has to be annotated as itself having the
possibility of throwing an EmptyMachineException
,
which is done by adding the words throws EmptyMachineException
to its signature, after the declaration of its parameters. As a result
of this, the method buyCokes
now has to be called either
within a try
...catch
statement which catches the EmptyMachineException
it may throw, or within another method which is also annotated as
throwing that exception. In the program
UseDrinksMachines7A
the method is called from the main
method within a
try
...catch
which catches it.
The method buyCokes
in UseDrinksMachines7A
does not have a way of indicating how many Cokes were bought before the
machine ran out if out could not buy the full number specified. This
could not be given as the return value of the method because when a
method terminates by throwing an exception it does not give a return
value. However, the message inside an exception can be used to
communicate a value. The program below is a rather messy way of
dealing with this issue:
import java.util.Scanner; class UseDrinksMachines8A { public static void main(String[] args) { Scanner input = new Scanner(System.in); DrinksMachineA machine = new DrinksMachineA(50,10,10); System.out.print("Enter the sum of money you wish to spend on cokes: "); int amount = input.nextInt(); System.out.print("Enter the number of cokes you wish to buy: "); int number = input.nextInt(); try { buyCokes(number,amount,machine); System.out.println("You successfully bought "+number+" cokes"); } catch(EmptyMachineException e) { number = Integer.parseInt(e.getMessage()); System.out.println("You bought "+number+" cokes "); System.out.println("Then the machine ran out of cokes"); } int change = machine.pressChange(); System.out.println("You have "+change+"p left"); } public static void buyCokes(int num,int sum,DrinksMachineA mach) throws EmptyMachineException { int count=0; mach.insert(sum); try { for(count=0; count<num; count++) mach.pressCoke(); } catch(EmptyMachineException e) { throw new EmptyMachineException(""+count); } } }Here, the method
buyCokes
catches the
EmptyMachineException
and then throws a new one
whose message gives the number of Cokes bought. Since an
exception is an object, you can create an exception by the word
new
followed by a call to its constructor, which as we
have seen is the class name followed by the arguments to the constructor.
Exceptions have a zero-argument constructor, and a constructor which
takes a String
argument. To turn an int
value
into a String
, we join it to the empty string using
+
, so that means
new EmptyMachineException(""+count)
creates a
new EmptyMachineException
object whose string is the
string equivalent of the value in the int
variable
count
. The Java statement throw
followed
by an exception value (typically the creation of a new exception,
but it could be a variable already referring to an existing exception),
when executed causes the method it is in to be terminated and the
exception given thrown. Note the distinction between the keyword
throws
added to a method signature to list checked
exceptions it may throw, and the keyword throw
to
give a statement which throws an exception.
So in UseDrinksMachine8A
in the method
buyCokes
, the statement
throw new EmptyMachineException(""+count);causes a new exception to be thrown with its message the string of characters which when read as an integer is the number of cokes bought. Note that as it is within a
catch
part
which catches an EmptyMachineException
, the effect is
to catch one EmptyMachineException
and throw another.
This second one is then caught in the main
method,
and its message is converted to an integer using
Integer.parseInt
. Note, this program is not
intended as an example of good Java style. Exceptions should not
generally be used for standard communication, it would be much better
to rewrite the method buyCokes
so it just returns an
integer giving the number of Cokes actually bought. The point of this
program is just to demonstrate that it is possible in Java to create your
own exception object and throw it.
If a method call can throw a run time exception, you do not have to
write code to catch it or indicate that it will be thrown on. But
you can still write code to catch it if you want. The same
try
...catch
mechanism is used as with
checked exceptions. If a run time exception is not caught in the
method which calls the method which caused it, it is thrown upwards
to the method which called that method, and so on upwards until it is
either caught or the main
method is reached, and then
the program terminates. As a simple example, below we
go back to the use of the DrinksMachine
class where
a call of pressCoke()
on a DrinksMachine
object
representing a machine which has run out of Cokes returns the value
null
. If a Can
variable was set to the
result of a call of pressCoke()
, and the code had no
check to see if it is null
, then when a Can
method is called on it, a NullPointerException
will
be thrown. In the following example, this is detected by catching
the NullPointerException
:
class UseDrinksMachines5 { public static void main(String[] args) { DrinksMachine machine = new DrinksMachine(50,0,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"); try { myCan.drink(); System.out.println("I drink from the can"); } catch(NullPointerException e) { System.out.println("The machine was empty of Cokes"); } } }In fact the code for
Can
was written so that
attempting to drink from a can which is already empty (meaning the
method drink()
has already been called on it) will result
in an EmptyCanException
being thrown. As this was written
to be a run time exception, there was no necessity to write code
which dealt with the possibility of this exception being thrown from
a call to the drink()
method on a Can
object.
This is not really good Java practice, since exceptions representing
problems in the world being modelled by the system should be checked
exceptions, with run time exceptions used only for programming errors.
However, it was done this way in order to enable simple use of the
DrinksMachine
example without having to consider
exceptions.
Below is some code which shows an EmptyCanException
being
caught:
class UseDrinksMachines6 { public static void main(String[] args) { try { DrinksMachine machine = new DrinksMachine(50,0,10); Can aCan = new Can("coke"); aCan.drink(); machine.loadCoke(aCan); 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"); System.out.println("I drink from the can"); myCan.drink(); System.out.println("Now I have "+myCan+" and "+myChange+"p"); System.out.println("I drink from the can again"); myCan.drink(); System.out.println("And now I have "+myCan+" and "+myChange+"p"); } catch(NullPointerException e) { System.out.println("Error: method called on null"); } catch(EmptyCanException e) { System.out.println("Error: attempt to drink from empty can"); } } }This shows that a
try
...catch
statement can
have more than one catch
part. It can have separate
catch
parts to catch the different sorts of exceptions that
may be thrown in the try
part. The code is actually
set up so that the first call of myCan.drink()
throws
an EmptyCanException
exception, because it represents
a situation where an empty can has been put into the machine. It also
shows why it is bad to make shortcuts by using null
to
cover special situations. A NullPointerException
might
be thrown if the variable myCan
is set to null
to represent no can being delivered by pressing the Coke button. But it
could also be caused by any programming error which resulted in a method
being called on an object variable set to null
. The error
message printed if a NullPointerException
is caught
reflects the fact that it is really a programming error, a method called
on null
. However, a message like this is meaningless to
the user of a system who isn't interested in the code underneath, and
doesn't even know about it. You may experience strange error messages even
in commercial software which represents something intended for the programmer
accidentally making it through to the end-user and it has probably just
confused and/or annoyed you. Such things should not happen. But it would
also be bad if a meaningful but wrong error message was given because the
programmer assumed a particular run time exception could only be caused by
a particular error in the world it was modelling, but it was actually caused
by programming error.
If you experiment with the DrinksMachine
class, you will find
that if pressCoke()
or pressFanta()
is called
on a DrinksMachine
object when the balance (as given by
calling getbalance()
on the object is
less than the price of a drink, then null
is returned,
just as it is if the machine is empty of drinks. If you experiment with
the DrinksMachineA
class, you will find that an
EmptyMachineException
is thrown if pressCoke()
or pressFanta()
is called on a DrinksMachineA
object when the balance on the object is less than the price of a drink.
To Java there is no mistake here, the Java compiler has no understanding
of the human meaning we may give to variable names or classes. However,
we can see this is a mistake, clearly it is misleading because
the name of the exception thrown by pressCoke()
and
pressFanta()
in DrinksMachineA
leads us to
believe it represents one possibility in the world we are modelling, whereas
really it represents two: either the machine has run out of the required
sort of drink, or we have not put enough money into it. This has led us to
write programs which behave very misleadingly, they print error messages
which do not represent what has really happened, telling us the machine is
empty when the real problem may be that not enough money was put in.
To resolve this problem, another modified version of DrinksMachine
is available, from the class file DrinksMachineB.class
. In
this case, calling the methods pressCoke()
and
pressFanta()
can cause exceptions of two
different types to be thrown, either EmptyMachineException
or NotEnoughMoneyException
, with the latter having the
class file NotEnoughMoneyException.class
. This means the
headers for these methods in DrinksMachineB
are:
Can pressCoke() throws EmptyMachineException, NotEnoughMoneyException Can pressFanta() throws EmptyMachineException, NotEnoughMoneyExceptionIt means a method in which either of these methods are called must be written to deal with the possibility of either exception. Code could be written to catch both, to throw both, or to catch one and throw the other. Below is a version of the simple
UseDrinksMachines1.java
program which catches both:
class UseDrinksMachines1B { public static void main(String[] args) { DrinksMachineB machine = new DrinksMachineB(80,10,10); System.out.println("I insert 50p into the drinks machine"); machine.insert(50); System.out.println("I press the button labelled \"Coke\""); try { Can myCan; 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"); } catch(EmptyMachineException e) { System.out.println("The machine is empty"); System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have "+myChange+"p"); } catch(NotEnoughMoneyException e) { System.out.println("I did not put enough money in"); System.out.println("I needed another "+e.getMessage()+"p"); System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have "+myChange+"p"); } System.out.println("I have finished"); } }As you can see, it requires a
try
...catch
statement with two catch
parts, one to catch each of the
exceptions which may be thrown. We also need to add code to catch a
NotEnoughMoneyException
into our modified version of
UseDrinksMachines2A.java
even though this exception could never be thrown as the code in the loop
ensures the pressCoke
method is not called unless the balance
is sufficient:
import java.util.Scanner; class UseDrinksMachines2B { public static void main(String[] args) { Scanner input = new Scanner(System.in); DrinksMachineB machine = new DrinksMachineB(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,DrinksMachineB mach) { int count=0; mach.insert(sum); try { while(mach.getBalance()>=mach.getPrice()) { mach.pressCoke(); count++; } } catch(EmptyMachineException e) { } catch(NotEnoughMoneyException e) { } return count; } }The Java compiler is not intelligent enough to know a
NotEnoughMoneyException
will never be thrown,
it requires code to deal with it so long as a method which declares
that it throws it is called.
This code makes repeated calls of the method getPrice
on the
DrinksMachine
object. However, if we assume that method always
return the same value, that is unnecessary. We could just make one call and
save the result in a variable. That would give the following code for the
method:
public static int spendOnCokes(int sum,DrinksMachineB mach) { int count=0; int price=mach.getPrice(); mach.insert(sum); try { while(mach.getBalance()>=price) { mach.pressCoke(); count++; } } catch(EmptyMachineException e) { } catch(NotEnoughMoneyException e) { } return count; }You should always avoid making repeated method calls in situations where you know each call will return the same value as the previous one. Saving the result in a variable and using the variable instead of repeating the call saves unnecessary time in calling the method again. In the code here, the time saved would be trivial, but if a method call involves a long calculation it could make a big improvement. Obviously it should be done only when we are sure that there is nothing which could cause repeated calls of the method to return different values.
A minor thing to note here is that a variation of the syntax for catching exceptions was
introduced in the version of Java called
Java 7.
It enables a single catch
part to list several exception types, and is used
when the same action should take place for all the exception types. Using this variation,
the method would be written:
public static int spendOnCokes(int sum,DrinksMachineB mach) { int count=0; int price=mach.getPrice(); mach.insert(sum); try { while(mach.getBalance()>=price) { mach.pressCoke(); count++; } } catch(EmptyMachineException | NotEnoughMoneyException e) { } return count; }
Last modified: 22 February 2019