interface DrinksMachine { public void addMoney(int pence); public int showMoney(); public int showPrice(); public String pressCoke(); public String pressFanta(); }The methods
pressCoke
and pressFanta
represent
pressing buttons to give us the appropriate drinks. The strings these
methods return represent the output from the machine, words describing
it in English. You can find this file and the other mentioned in this
page of notes in /import/teaching/BSc/1st/ItP/drinksmachines
.
Now let us consider a first attempt to write a class which implements this interface:
class SimpleDrinksMachine implements DrinksMachine { private int balance,price; public SimpleDrinksMachine(int p) { price=p; } public void addMoney(int pence) { balance+=pence; } public int showMoney() { return balance; } public int showPrice() { return price; } public String pressCoke() { int change=balance-price; balance=0; return "Coke + "+change+"p"; } public String pressFanta() { int change=balance-price; balance=0; return "Fanta + "+change+"p"; } public void changePrice(int newprice) { price=newprice; } }This implements all the methods in the interface, as is required, and also adds an extra one
changePrice
which enables us
to change the price of a drink from a machine. Because changePrice
is in SimpleDrinksMachine
but not in DrinksMachine
,
if we have a variable d1
of type SimpleDrinksMachine
we can call, say, d1.changePrice(50)
to represent changing
the price of the drinks provided by the SimpleDrinksMachine
object referred to by d1
. However, if we have a variable
d2
of type DrinksMachine
the call
d2.changePrice(50)
makes no sense and will be rejected by the
Java compiler, even if d2
happens to refer to a
SimpleDrinksMachine
object. We could, however, view the
object d2
refers to as a SimpleDrinksMachine
by
typecasting, so ((SimpleDrinksMachine)d2).changePrice(50)
would be accepted by the Java compiler. It would throw an exception
of the type ClassCastException
if at the point of
execution d2
did not refer to an object of type
SimpleDrinksMachine
(or one of a type that extends
SimpleDrinksMachine
).
The constructor for a SimpleDrinksMachine
takes one argument, an
int
which gives the price of drinks from that machine
initially. However, a SimpleDrinksMachine
object set up
to dispense drinks at one price can be changed to dispense them at
another because of the changePrice
method.
Here is a simple example showing the use of DrinksMachine
variables to refer to SimpleDrinksMachine
objects:
import java.io.*; class UseDrinksMachine2 { // Same as UseDrinksMachine1, but shows use of DrinksMachine type public static void main(String[] args) throws NumberFormatException, IOException { DrinksMachine cheap = new SimpleDrinksMachine(25); DrinksMachine expensive = new SimpleDrinksMachine(80); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int amount; System.out.print("\nHow much do you want to put in the cheap machine? "); amount = Integer.parseInt(in.readLine()); cheap.addMoney(amount); System.out.print("When you press the Coke button you get "); System.out.println(cheap.pressCoke()); System.out.print("How much more do you want to put in? "); amount = Integer.parseInt(in.readLine()); cheap.addMoney(amount); System.out.println("The amount showing is: "+cheap.showMoney()+"p"); System.out.print("When you press the Fanta button you get "); System.out.println(cheap.pressFanta()); System.out.print("\nHow much do you want to put in the expensive machine?"); amount = Integer.parseInt(in.readLine()); expensive.addMoney(amount); System.out.print("How much more do you want to put in? "); amount = Integer.parseInt(in.readLine()); expensive.addMoney(amount); System.out.println("The amount showing is: "+expensive.showMoney()+"p"); System.out.print("When you press the Coke button you get "); System.out.println(expensive.pressCoke()); System.out.println(); } }The file (contents not shown on this page)
UseDrinksMachine1.java
shows the variables of type SimpleDrinksMachine
, while the
file UseDrinksMachine2a.java
demonstrates type casting
used to call the changePrice
method.
The scenarios in these files show the use of two machines with
different prices. Some money is put into the first machine, and the Coke
button is pressed. Then some more money is put in, and the program shows the
amount currently in the machine. Then the Fanta button is pressed.
After this, the second machine is used, with some money put in, then some
more money, then its Coke button is pressed.
DrinksMachine
SimpleDrinksMachine
class gives as output from
the button-pressing methods the drink and the change (as a string).
However, the class wasn't written to check the amount entered is
enough, so the simulation allows you to put less money than the
price of a drink into the machine and get a negative amount in change.
To correct this, we introduce a new implementation of DrinksMachine
which we call DrinksMachineA
in which the button pressing
methods return the string "Nothing"
when they are used
in the situation simulating less than the price of a drink entered into
the machine. Here is the code for DrinksMachineA
:
class DrinksMachineA implements DrinksMachine { // A drinks machine that makes sure you pay enough protected int balance,price; public DrinksMachineA(int p) { price=p; } public void addMoney(int pence) { balance+=pence; } public int showMoney() { return balance; } public int showPrice() { return price; } public String pressCoke() { if(balance<price) return "Nothing"; else { int change=balance-price; balance=0; return "Coke + "+change+"p"; } } public String pressFanta() { if(balance<price) return "Nothing"; else { int change=balance-price; balance=0; return "Fanta + "+change+"p"; } } public void changePrice(int newprice) { price=newprice; } }Code showing the same scenario as given above, but using drinks machines simulated by
DrinksMachineA
objects is given in the
file UseDrinksMachine3.java
An alternative design for a drinks machine might not automatically
give change with a drink. Rather there is a separate change button
which you press if you want your change, otherwise the change can be
kept in the machine for buying further drinks. This sort of machine is
simulated by the class DrinksMachineB
given below:
class DrinksMachineB implements DrinksMachine { // A drinks machine with a Change button private int balance,price; public DrinksMachineB(int p) { price=p; } public void addMoney(int pence) { balance+=pence; } public int showMoney() { return balance; } public int showPrice() { return price; } public String pressCoke() { if(balance<price) return "Nothing"; else { balance-=price; return "Coke"; } } public String pressFanta() { if(balance<price) return "Nothing"; else { balance-=price; return "Fanta"; } } public String pressChange() { int change=balance; balance=0; return change+"p"; } public void changePrice(int newprice) { price=newprice; } }The file
UseDrinksMachine4.java
shows the same scenario
as before, but using drinks machines of type DrinksMachineB
.
If we want a scenario which includes the change button being pressed,
we must either use variables of type DrinksMachineB
to
call the pressChange
method on, as is done in
UseDrinksMachine5.java
, or use typecasting is is done in
UseDrinksMachine5a.java
which is shown below:
import java.io.*; class UseDrinksMachine5a { // Uses machines of type DrinksMachineB, and uses Change button // Uses variables of type DrinkMachine and type casting public static void main(String[] args) throws NumberFormatException, IOException { DrinksMachine cheap = new DrinksMachineB(25); DrinksMachine expensive = new DrinksMachineB(80); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int amount; System.out.print("\nHow much do you want to put in the cheap machine? "); amount = Integer.parseInt(in.readLine()); cheap.addMoney(amount); System.out.print("When you press the Coke button you get "); System.out.println(cheap.pressCoke()); System.out.print("How much more do you want to put in? "); amount = Integer.parseInt(in.readLine()); cheap.addMoney(amount); System.out.println("The amount showing is: "+cheap.showMoney()+"p"); System.out.print("When you press the Fanta button you get "); System.out.println(cheap.pressFanta()); System.out.print("When you press the Change button you get "); System.out.println(((DrinksMachineB)cheap).pressChange()); System.out.print("\nHow much do you want to put in the expensive machine? "); amount = Integer.parseInt(in.readLine()); expensive.addMoney(amount); System.out.print("How much more do you want to put in? "); amount = Integer.parseInt(in.readLine()); expensive.addMoney(amount); System.out.println("The amount showing is: "+expensive.showMoney()+"p"); System.out.print("When you press the Coke button you get "); System.out.println(expensive.pressCoke()); System.out.print("When you press the Change button you get "); System.out.println(((DrinksMachineB)expensive).pressChange()); System.out.println(); } }A further implementation of
DrinksMachine
can be found in
DrinksMachineC.java
. The previous implementations assumed
that machines had an infinit supply of cans of drink. The
DrinksMachineC
class simulates a machine with a limited
number of cans. It has two extra methods, loadFantas
and
loadCokes
which simulate loading the machine with more
cans of the two types of drinks. An example of the use of machines of
this type (including loading the machines with drinks) can be found in
UseDrinksMachine6.java
.
UseDrinksMachine7.java
gives a scenario in
which the price of drinks from two machines is compared, and the
cheaper of the two machines is used to get a drink. In this case
since the two machines are set up with prices which aren't changed,
we know which has the cheaper price when we run the program, but we
could imagine the same situation occurring in a more complex program
where we wouldn't know at the time we wrote the program which of the two
machines being considered is cheapest. Here is the code (with line numbers
added):
1 import java.io.*; 2 3 class UseDrinksMachine7 { 4 // Shows use of if statement 5 6 public static void main(String[] args) throws NumberFormatException, IOException 7 { 8 DrinksMachine machine1 = new DrinksMachineA(25); 9 DrinksMachine machine2 = new DrinksMachineA(80); 10 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 11 int amount; 12 String reply; 13 14 System.out.print("How much do you want to put in a machine? "); 15 amount = Integer.parseInt(in.readLine()); 16 if(machine1.showPrice()<machine2.showPrice()) 17 { 18 machine1.addMoney(amount); 19 reply = machine1.pressCoke(); 20 } 21 else 22 { 23 machine2.addMoney(amount); 24 reply = machine2.pressCoke(); 25 } 26 System.out.print("When you press the coke button on the cheaper machine "); 27 System.out.println("you get: "+reply); 28 } 29 }The price of the two machines is compared on line 16. The program in
UseDrinksMachine7a.java
behaves exactly the same as
the one in UseDrinksMachine7.java
. The only difference
(which does not affect its behaviour) is that the comparison is done
using a separate static method which takes two DrinksMachine
objects as arguments and returns a boolean, true
if the
first machine is cheaper than the second, false otherwise. An alternative
is to use a static method which returns a reference to the cheaper of the
two machines which it took as arguments, this is done in
UseDrinksMachine7b
, shown below:
1 import java.io.*; 2 3 class UseDrinksMachine7b { 4 // Shows use of if statement in separate static method 5 // which returns a DrinksMachine reference 6 7 public static void main(String[] args) throws NumberFormatException, IOException 8 { 9 DrinksMachine machine1 = new DrinksMachineD(25); 10 DrinksMachine machine2 = new DrinksMachineA(80); 11 DrinksMachine cheaper; 12 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 13 int amount; 14 15 System.out.print("How much do you want to put in a machine? "); 16 amount = Integer.parseInt(in.readLine()); 17 cheaper = cheapestMachine(machine1,machine2); 18 cheaper.addMoney(amount); 19 System.out.print("When you press the coke button on the cheaper machine "); 20 System.out.println("you get: "+cheaper.pressCoke()); 21 } 22 23 public static DrinksMachine cheapestMachine(DrinksMachine a,DrinksMachine b) 24 { 25 if(a.showPrice()<b.showPrice()) 26 return a; 27 else 28 return b; 29 } 30 }Here, the static method is on lines 23 to 29. Line 18 will set the variable
cheaper
to be an alias
of either machine1
or machine2
, whichever is
the cheaper. The pressCoke
method is then used on this machine
on line 20.
Note that UseDrinksMachine7b
uses another implementation
of DrinksMachine
, this time DrinksMachineD
. If
you look at the code for this in /import/teaching/BSc/1st/ItP/drinksmachines
you will see that it is the same as DrinksMachineA
except
the pressCoke
and pressFanta
methods return
different strings. It represents a machine where the drinks returned are
Pepsi and Tango rather than Coke and Fanta. However, as it has to
implement the DrinksMachine
interface, it must still have
methods called pressCoke
and pressFanta
.
Furthermore, on line 20 the variable cheaper
could refer
to an object of type DrinksMachineA
or DrinksMachineD
.
This is acceptable, since cheaper
is of type DrinksMachine
and both DrinksMachineA
and DrinksMachineD
implement type DrinksMachine
. However, it means that
line 20 could cause a message about Coke or Pepsi to be printed, depending
on which type of machine cheaper
refers to.
UseDrinksMachine7c.java
shows the use of the ternary
operator to give the same effect as that given in UseDrinksMachine7b
.
UseDrinksMachine7d.java
shows how the static method that finds
the cheaper of two drinks machines could be in a separate class called
DrinksMachineFuncs
. You will find a file for this class,
DrinksMachineFuncs.java
in the same directory as the other
files mentioned here. The static method cheaper
is overloaded in this file, as there are two versions of it, one as used
in UseDrinksMachine7d
which takes two DrinksMachine
arguments, the other (whose use is demonstrated in UseDrinksMachine7e
)
which takes three DrinksMachine
arguments. Note that in all
these methods, an argument whose type is given in the signature as
DrinksMachine
will be set to refer to an object which is
of one of the types that implements DrinksMachine
.
DrinksMachineE
is a type which extends DrinksMachineA
. Here is its code:
class DrinksMachineE extends DrinksMachineA { // A version of DrinksMachineA with an extra button public DrinksMachineE(int p) { super(p); } public String pressSprite() { if(balance<price) return "Nothing"; else { int change=balance-price; balance=0; return "Sprite + "+change+"p"; } } }This means that a
DrinksMachineE
object has all the
methods of a DrinksMachineA
object, plus an additional
one, pressSprite
which takes no arguments and returns a
String
. Of course this extra method, pressSprite
can only be called when attached to a variable of type
DrinksMachineE
(or an expression which is known at compile
time to evaluate to a value of type DrinksMachineE
, or
to a variable of a type which extends DrinksMachineE
if
we introduced such a type). We could not attach a call to
pressSprite
to a variable of type DrinksMachineA
even if we knew it referred to an object of type DrinksMachineE
,
because the method pressSprite
is not defined in
DrinksMachineA
. There is a file called
UseDrinksMachine9.java
in the directory
/import/teaching/BSc/1st/ItP/drinksmachines
which
demonstrates use of DrinksMachineE.java
objects.
One interesting point to note is that while a class may extend only one other class (single inheritance), it may implement any number of interfaces. This is a limited form of what is known as multiple inheritance. Some other languages, C++ is an example, allow classes to extend more than one class, but this can be problematical since it raises the question what happens when a class inherits two methods with the same signature but different code. There is no problem when there is multiple inheritance from interfaces, as they only have signatures.
As an example, here's an interface defining a type BeverageMachine
:
interface BeverageMachine { public void addMoney(int pence); public int showMoney(); public int showPrice(); public String pressTea(); public String pressCoffee(); }The first three methods here are the same as in the interface
DrinksMachine
, the last two are different. Just
for demonstration, you will find a file BeverageMachineA.java
,
which contains an implementation of BeverageMachine
. For
simplicity it has been written just like DrinksMachineA
,
replacing Coke
and Fanta
with Tea
and Coffee
. There is also a file UseBeverageMachine
which shows the use of an object of this type.
The class DrinksMachineF
implements both the interfaces
DrinksMachine
and BeverageMachine
. It
can do this so long as it has a method for each of the signatures
in the two interfaces (only one method though for those signatures that
appear in both interfaces). The code for the class has heading:
class DrinksMachineF implements DrinksMachine,BeverageMachineThe program
UseDrinksMachine10
shows the use of an
object of type DrinksMachineF
, including calling methods
that are from DrinksMachine
only, from BeverageMachine
only, and from both. The program UseDrinksMachine11
shows
how an object of type DrinksMachineF
can be referred to by
both a variable of type DrinksMachine
and a variable of
type BeverageMachine
. In this case, a call to a method which
is listed only in the interface DrinksMachine
cannot be
attached to the variable of type BeverageMachine
and
vice versa.
Last modified: 23 Jan 2001