In a previous set of notes, we
looked at code which implemented
objects of a class DrinksMachine
. This was intended to
simulate a type of machine with buttons for taking money, dispensing two
different varieties of cans of drink ("coke" and "fanta"), and returning
change. Suppose we want to simulate another type of drinks machine which
is like the previous one, but which has a button for dispensing a third
variety of drink ("sprite") as well as the two varieties of the
previous machine. We can do this through inheritance. Here is the code
for it:
class ExtDrinksMachine1 extends DrinksMachine { private ArrayList<Can> sprites; public ExtDrinksMachine1(int p) { super(p); sprites = new ArrayList<Can>(); } public ExtDrinksMachine1(int p,int c, int f,int s) { super(p,c,f); sprites = new ArrayList<Can>(); for(int i=0; i<s; i++) loadSprite(new Can("sprite")); } public Can pressSprite() { if(sprites.size()>0&&balance>=price) { Can can = sprites.get(0); sprites.remove(0); balance=balance-price; cash=cash+price; return can; } else return null; } public void loadSprite(Can can) { sprites.add(can); } public boolean spritesEmpty() { return sprites.size()==0; } }This defines objects of a class called
ExtDrinksMachine1
. The
words extends DrinksMachine
in the header to this class
indicate that this class is an extension by inheritance of the class
DrinksMachine
. On an object of class ExtDrinksMachine1
you can call all the methods for the class DrinksMachine
plus the additional methods listed in the code above. So the methods you
can call coming from DrinksMachine
are:
insert(int) getBalance() collectCash() getPrice() pressChange() pressCoke() pressFanta() loadCoke(Can) loadFanta(Can) cokesEmpty() fantasEmpty() setPrice(int)Where methods take arguments, the type of the argument is given in this listing. You do not have to give code for these methods in the class
ExtDrinksMachine1
because a call on them on an object
of this class will automatically use the code from the class
DrinksMachine
due to the extends DrinksMachine
in the header. The methods listed in the code for
ExtDrinksMachine1
are only those which are extra to the ones
given in DrinksMachine
and they are:
pressSprite() loadSprite(Can) spritesEmpty()An object of type
ExtDrinksMachine1
has within it all the
variables which objects of type DrinksMachine
have within
them and which are listed in the code for that class, integers
price
, balance
and cash
and two
arrayLists of Can
objects, cokes
and fantas
.
It also has within it an extra variable which is an arrayLists of
Can
objects, called sprites
. This is indicated
by the declaration in the code for ExtDrinksMachine1
:
private ArrayList<Can> sprites;Note that in order for the methods given in class
ExtDrinksMachine1
to refer to the variables price
, balance
and
cash
those variables have to be declared as
protected
rather than private
in the class
DrinksMachine
. A variable which is declared in a class
as private
exists in objects of a class which extends it,
but cannot be accessed in the code which extends it.
You can see that the extra methods in the class ExtDrinksMachine1
manipulate the arrayList called sprites
in the same way
that the related methods in the class DrinksMachine
manipulate
the variables cokes
and fantas
.
The constructors for ExtDrinksMachine1
are
public ExtDrinksMachine1(int p) { super(p); sprites = new ArrayList<Can>(); }and
public ExtDrinksMachine1(int p,int c, int f,int s) { super(p,c,f); sprites = new ArrayList<Can>(); for(int i=0; i<s; i++) loadSprite(new Can("sprite")); }The first sets up an extended drinks machine which contains no cans. Its only argument is the initial price of drinks for the machine. The second constructor sets up an extended drinks machine which has cans within it, its four arguments are the initial price, and the initial number of cans of coke, fanta and sprite. Note that if the first statement in a constructor consists of the word
super
followed by some arguments enclosed within rounded brackets the result
is to use the code of the constructor of the class which the class extends
to fill values of the variables in the new object created by the
constructor. So in the first constructor for ExtDrinksMachine1
,
the statement super(p)
calls the code in the constructor for
DrinksMachine
which has one argument of type int
.
This is:
public DrinksMachine(int p) { price = p; balance = 0; cash = 0; cokes = new ArrayList<Can>(); fantas = new ArrayList<Can>(); }so the result is to set the new object's
price
variable to
the value p
, its balance
and cash
variables to 0
and its cokes
and fantas
variables to new arrayLists of Can
s of initial size 0
.
The additional code in the first constructor for ExtDrinksMachine1
initialises the variable sprites
in the new object being
created to a new arrayList of Can
s of initial size 0
.
In the second constructor for ExtDrinksMachine1
, the
call super(p,c,f)
uses the code from the constructor for
DrinksMachine
which takes three arguments of type int
to set the value of the price
, balance
and
cash
variables, and to create c
new
Can
objects to put in the cokes
arrayList and
f
new Can
objects to put in the fantas
arrayList. After executing this code from the constructor of
DrinksMachine
, this constructor of ExtDrinksMachine1
sets the variable sprites
to a new empty arrayList of
Can
s, and then adds s
new Can
objects
to it.
Classes and subclasses
Up to this point you might think that every Java object has its own distinct
type. But actually the situation is more complex than this. A Java object
may have more than one type. The reason for this is that Java has the
concept of "subclass". Every object of a type which extends
some other type can be considered an object of both the original
type and the extended type. In the example we have been discussing,
every object of type ExtDrinksMachine1
is also of type
DrinksMachine
. The consequence of this is that a variable
of type DrinksMachine
can be made to refer to an object of
type ExtDrinksMachine1
. Also a method which has a parameter
of type DrinksMachine
can take an object of type
ExtDrinksMachine1
as its matching argument. We say that
ExtDrinksMachine1
is a subclass of DrinksMachine
,
or that DrinksMachine
is a superclass of
ExtDrinksMachine1
.
As an example, we saw previously
a static method called cheaper
which took two DrinksMachine
objects as its arguments and returned a reference to whichever was the cheapest.
Here is its code:
public static DrinksMachine cheaper(DrinksMachine m1,DrinksMachine m2) { if(m1.getPrice()<m2.getPrice()) return m1; else return m2; }This will still work even if one or both of its arguments are of type
ExtDrinksMachine1
. For example, if we have the code fragment:
DrinksMachine mach1 = new DrinksMachine(m); ExtDrinksMachine1 mach2 = new ExtDrinksMachine1(n); DrinksMachine mach3 = cheaper(mach1,mach2);then
mach3
will be an alias of either mach1
or
mach2
depending on which of m
or n
is
the lower.
When an object is referred to through a variable, we have the concept of
its actual type and its apparent type. This is because
a variable which is declared of one type may refer to an object which was
created through a constructor of a subclass type. So, after the above
code fragment is executed, if n
is less than m
the variable mach3
refers to the object created by the call
new ExtDrinksMachine1(n)
. The "apparent type" is the
type the variable is declared as, but the actual type of the object is the
name that was used to construct it. Here the apparent type would be
DrinksMachine
and the actual type would be
ExtDrinksMachine1
.
Note that if a method call is attached to a variable, it must be a method which appropriate for the apparent type of that variable. So, following the execution of the code fragment above, we could have
mach3.pressCoke();but not
mach3.pressSprite();since the variable
mach3
is of type DrinksMachine
and the method pressCoke
is declared in DrinksMachine
but the method pressSprite
is only declared in its subclass
ExtDrinksMachine1
. When the code is compiled, the Java
compiler cannot tell whether mach3
would refer to an
object of actual type ExtDrinksMachine1
. Remember that although
every object of type ExtDrinksMachine1
is also of type
DrinksMachine
, it is not the case that every object
of type DrinksMachine
is also of type
ExtDrinksMachine1
.
If you have a reference to an object through a variable of one type,
and you know the actual type of that object is some particular
subtype, you can treat it as an object of that subtype by using
casting. This is done by preceding the reference to the object
by the name of the subtype enclosed within rounded brackets. For example,
if we have variable mach4
declared of type
ExtDrinksMachine1
:
ExtDrinksMachine1 mach4;then we can have:
mach4 = (ExtDrinksMachine1) mach3;If this were executed when
mach3
referred to an object whose
actual type was not ExtDrinksMachine1
an exception of type
ClassCastException
would be thrown. Note that assignment the
other way round:
mach3 = mach4;does not require casting as it is always possible to assign an object of a subclass to a variable of its superclass.
If you want to test whether a variable of one type refers to an object
whose actual type is a subclass, Java has the keyword instanceof
to do that. The test r instanceof t
, where r
is
a reference to an object and t
is a type name, evaluates to
true
if r
has the type t
(or a
type which is a subclass of t
) and to
false
otherwise. Here is an example of a code fragment
which uses that:
Can c; if(mach3 instanceof ExtDrinksMachine1) c = ((ExtDrinksMachine1) mach3).pressSprite(); else c = mach3.pressFanta();which has the effect of setting
c
to the result of
pressing the "sprite" button of the machine referred to by
mach3
if it has a "sprite" button, otherwise setting it
to the result of pressing the "fanta" button. Note that
((ExtDrinksMachine1) mach3).pressSprite()
combines
casting the variable mach3
to the type
ExtDrinksMachine1
and then calling a method from that
subclass without the need for an extra variable of that subclass.
It has the effect of viewing the object referred to by mach3
as of type ExtDrinksMachine1
and then calling the
method pressSprite()
on the result.
It is necessary to have both sets of brackets to do this since the
method attachment operator has higher precedence than the casting operator.
This means that just (ExtDrinksMachine1) mach3.pressSprite()
would be interpreted as (ExtDrinksMachine1) (mach3.pressSprite())
,
that is an attempt to call pressSprite()
on mach3
and then view the result as of type ExtDrinksMachine1
(which does
not make sense).
Here is some code you can use to test ExtDrinksMachine1
objects:
import java.util.Scanner; class UseDrinksMachines5 { public static void main(String[] args) { int p,c,f,s; Scanner input = new Scanner(System.in); System.out.println("Machine 1 is a standard machine"); System.out.print("Enter the price for drinks on machine 1: "); p = input.nextInt(); System.out.print("Enter the number of cokes in machine 1: "); c = input.nextInt(); System.out.print("Enter the number of fantas in machine 1: "); f = input.nextInt(); DrinksMachine mach1 = new DrinksMachine(p,c,f); System.out.println("Machine 2 is an extended machine"); System.out.print("Enter the price for drinks on machine 2: "); p = input.nextInt(); System.out.print("Enter the number of cokes in machine 2: "); c = input.nextInt(); System.out.print("Enter the number of fantas in machine 2: "); f = input.nextInt(); System.out.print("Enter the number of sprites in machine 2: "); s = input.nextInt(); ExtDrinksMachine1 mach2 = new ExtDrinksMachine1(p,c,f,s); DrinksMachine cheaper = DrinksMachineOps.cheaper(mach1,mach2); System.out.print("Enter the amount of money you wish to spend on cokes "+ "on the cheaper machine: "); int amount = input.nextInt(); int cokes = DrinksMachineOps.spendOnCokes(amount,cheaper); amount = cheaper.pressChange(); System.out.println("You have "+cokes+" cokes and "+amount+"p change"); mach2.insert(amount); System.out.println("Put the change in Machine 2 and press the Sprite button"); Can can = mach2.pressSprite(); amount = mach2.pressChange(); if(can==null) System.out.println("No sprites available"); else System.out.println("You have a "+can+" and "+amount+"p change"); } }This code assumes the static method
cheaper
and a static
method spendOnCokes
are in a class called
DrinksMachinesOps
. We used the method spendOnCokes
previously for illustration.
Here is some code for it:
public static int spendOnCokes(int sum,DrinksMachine mach) { int count=0; mach.insert(sum); while(!mach.cokesEmpty()&&mach.getBalance()>=mach.getPrice()) { mach.pressCoke(); count++; } return count; }The idea of this method is to simulate putting a certain amount of money in a machine then keep pressing the "coke" button until either the money runs out, or the machine runs out of cokes. The code in the file
UseDrinksMachine5.java
performs this operation on the
cheaper machine, so it is only when the code is run that it is determined
whether the method spendOnCokes
is executed on an object
of type ExtDrinksMachine1
or an object which is just of
type DrinksMachine
. Then any remaining money is put into the
machine with the "sprite" button, that button is pressed and the "change" button
on that machine is pressed.
You can find the files this example needs in the directory
~mmh/DCS128/code/drinksmachinesThe files required are
DrinksMachine.java
,
ExtDrinksMachine1.java
,
DrinksMachineOps.java
and
UseDrinksMachines5.java
.
To demonstrate this, we will consider another extension of the original
DrinksMachine
class. We will name this ExtDrinksMachine2
.
This represents a drinks machine which looks just like the original drinks
machine. However, this drinks machine has a link to a supplier company. When
it runs out of cokes or fantas, it sends a message to the supplier company
informing it of this. Here is the code:
class ExtDrinksMachine2 extends DrinksMachine { private DrinksCompany supplier; private String identity; public ExtDrinksMachine2(DrinksCompany link,String id,int p) { super(p); identity = id; supplier = link; } public ExtDrinksMachine2(DrinksCompany link,String id,int p,int c, int f) { super(p,c,f); identity = id; supplier = link; } public Can pressCoke() { if(cokes.size()>0&&balance>=price) { Can can = cokes.get(0); cokes.remove(0); if(cokes.size()==0) supplier.cokesEmpty(this); 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); if(fantas.size()==0) supplier.fantasEmpty(this); balance=balance-price; cash=cash+price; return can; } else return null; } public String getIdentity() { return identity; } }Objects of type
ExtDrinksMachine2
have the variables and
methods of objects of type DrinksMachine
. This is given
by the extends DrinksMachine
bit, there is no need for any more.
However, alternative code is given for the methods pressCoke
and pressFanta
. Also there are two extra variables in an
object of type ExtDrinksMachine2
on top of the ones inherited
from DrinksMachine
, these extra variables are
identity
of type String
and supplier
of type DrinksCompany
. There is one extra method,
getIdentity
which returns the value of the variable
identity
. The constructors for ExtDrinksMachine2
have arguments which are used to set the identity
and
supplier
variables.
The code for the method pressCoke
in the class
ExtDrinksMachine2
is like the code for
pressCoke
in DrinksMachine
with the
addition that if after removing an item from the arrayList
cokes
the size of this arrayList falls to 0, the
method cokesEmpty
with argument this
is
called on the DrinksCompany
object referenced by the
supplier
variable. Similar applies to the code for the
method pressFantas
. Note this use of the word this
to mean "the object this method is called on". For example, if the call
mach2.pressCoke()
is made, and the object referred to by
mach2
is an object of type ExtDrinksMachine2
with the cokes
variable referring to an arrayList of length 1,
then the call to supplier.cokesEmpty(this)
which will
result will in effect be a call supplier.cokesEmpty(mach2)
.
The method sends a reference to the whole object to the object referred to
by the ExtDrinksMachine2
object's supplier
variable.
As we saw previously when discussing the ExtDrinksMachine1
type,
a variable of a particular class may reference an object of a subclass of
that class. So a variable of type DrinksMachine
may
reference an object of type ExtDrinksMachine1
or
ExtDrinksMachine2
. If the parameter to a method is given as
type DrinksMachine
that method is general for objects of type
DrinksMachine
or any of its subclasses, such as the two
given here ExtDrinksMachine1
and ExtDrinksMachine2
.
This raises an issue: suppose we have a variable of type
DrinksMachine
which happens to be storing a reference to an
object of actual type ExtDrinksMachine2
. Then suppose the
method pressCoke
is called on this variable. Which code will
be used to execute this method, the code from the DrinksMachine
class or the code from the ExtDrinksMachine2
class?
The answer is ExtDrinksMachine2
, this is because method calling
in Java works using a feature known as dynamic binding. So if a method
call is attached to a variable, the method must be one defined in the
class of that variable or a superclass as this is checked when the code is
compiled. However, when the code is run, the actual class of the
object the variable refers to (which may be a subclass of the class of the variable)
is looked at to see if there is a method in it which matches the call and that
method is used.
To illustrate this, let us consider a code fragment similar to that we considered above:
DrinksMachine mach1 = new DrinksMachine(m); ExtDrinksMachine mach2 = new ExtDrinksMachine(n); DrinksMachine mach3 = cheaper(mach1,mach2); mach3.pressCoke();When the last statement here is executed, which version of
pressCode
is used will depend on whether mach3
is an alias for
mach1
or for mach2
. If it is an alias for
mach2
, it will have the effect of calling the
cokesEmpty
method on the supplier
variable
of the object referred to by mach2
if that object's
cokes
arrayList drops to length 0. This is the behaviour we
would want to simulate real life machines. Two machines may look identical
with "coke" and "fanta" buttons. One is of the sort which contacts the
supplier when it runs out, the other is of the simpler sort which does not.
We would expect, when we press the "coke" button, for a machine to
react appropriately according to its own type, even if we do not know
which type the machine is.
Here is some code for testing the ExtDrinksMachine2
example:
import java.util.Scanner; class UseDrinksMachines6 { public static void main(String[] args) { int p,c,f; Scanner input = new Scanner(System.in); System.out.println("Machine 1 is a standard machine"); System.out.print("Enter the price for drinks on machine 1: "); p = input.nextInt(); System.out.print("Enter the number of cokes in machine 1: "); c = input.nextInt(); System.out.print("Enter the number of fantas in machine 1: "); f = input.nextInt(); DrinksMachine mach1 = new DrinksMachine(p,c,f); System.out.println("Machine 2 is an extended machine"); System.out.print("Enter the price for drinks on machine 2: "); p = input.nextInt(); System.out.print("Enter the number of cokes in machine 2: "); c = input.nextInt(); System.out.print("Enter the number of fantas in machine 2: "); f = input.nextInt(); DrinksCompany comp = new DrinksCompany(); ExtDrinksMachine2 mach2 = new ExtDrinksMachine2(comp,"no. 2",p,c,f); DrinksMachine cheaper = DrinksMachineOps.cheaper(mach1,mach2); System.out.print("Enter the amount of money you wish to spend on cokes "+ "on the cheaper machine: "); int amount = input.nextInt(); int cokes = DrinksMachineOps.spendOnCokes(amount,cheaper); amount = cheaper.pressChange(); System.out.println("You have "+cokes+" cokes and "+amount+"p change"); mach2.insert(amount); System.out.println("Put the change in Machine 2 and press the Fanta button"); Can can = mach2.pressFanta(); amount = mach2.pressChange(); if(can==null) System.out.println("No fantas given"); else System.out.println("You have a "+can+" and "+amount+"p change"); } }As in the previous example, it creates two machines, one of the standard type and one of the extended type, and runs the method
spendOnCokes
on the cheaper machine. Which is the cheaper machine is only determined
when the code is actually run. For this code to work, you will also need a
class which provides DrinksCompany
objects. Here is an example:
class DrinksCompany { public DrinksCompany() { } public void cokesEmpty(ExtDrinksMachine2 mach) { System.out.println("Machine "+mach.getIdentity()+" out of cokes"); for(int i=0; i<10; i++) mach.loadCoke(new Can("coke")); } public void fantasEmpty(ExtDrinksMachine2 mach) { System.out.println("Machine "+mach.getIdentity()+" out of fantas"); for(int i=0; i<10; i++) mach.loadFanta(new Can("fanta")); } }All that is required of a
DrinksCompany
object from the code in
the front-end UseDrinksMachines6
is that it can have the
methods cokesEmpty
and fantasEmpty
called on it,
with arguments of type ExtDrinksMachine2
. In this example,
the identity of the machine is used in a message that is printed on the
screen, and the machine is loaded with ten new cans. If the cheaper machine
is of type ExtDrinksMachines2
it will never run out of cokes,
because as soon as it does it is instantly replenished. This is because
when cokesEmpty
is called on the DrinksCompany
object the
code for it is executed, and execution returns to the code for pressCoke
and from that to the code for spendOnCokes
afterwards. A more
sophisticated simulation might have the DrinksCompany
object on
a separate "thread", so that its code is executed in its own time and a delay is
observed before the machine is replenished. However, that is beyond the scope
of this course.
The files this example requires are:
DrinksMachine.java
,
Can.java
,
EmptyCanException.java
ExtDrinksMachine2.java
,
DrinksCompany.java
,
DrinksMachineOps.java
and
UseDrinksMachines6.java
.
Last modified: 3 March 2006