.class
file to define them.
When using Java we can talk about the application of a class of objects, which is code that uses object of that class, and the implementation of an object, which is the code that is used to make the objects work when methods are called on them. These two aspects are united with the specification which describes how an object of a particular class works in terms of its interaction with the application code. The writer of the application code writes that code under the assumption that objects of the class work according to the specification, he or she does not need to know what happens underneath to make them work that way. The writer of the implementation code has to make sure objects of the class work according to the specification, but he or she does not need to know what else happens in the application code.
Breaking up code into separate well-defined portions like this is a key part of good programming style. It may not seem so apparent on the sort of small-scale programs you write when you first start learning to program, but remember the sort of computer programs which are developed and sold commercially are on a hugely greater scale, written by whole teams of people. It is impossible for any one person to have a full understanding of how all the code works, breaking down the program into well-defined parts is the only way to manage its development.
The object-oriented style of programming also encourages re-use of
code. This means that once a class of objects has been developed, that class
can be used again in other circumstances where that sort of object is required.
This means that it is not necessary to write every part of a program in
terms of the most basic stuctures of the programming language. We have
already seen software re-use when we have used code from Java's libraries.
If we want something that works like an array, but whose size may change,
for example, we do not start from scratch and think how we could write
code to do that. Instead, we pick the class
ArrayList
from
the Java library, and make use of it, it runs using the code already
written by the developers of the Java system.
However, we cannot assume there will always be a suitable class already defined for every need we might have. It is important as Java programmers to be able to develop our own classes when necessary.
static
in Java. If we have a method
with the header
static R meth(T1 p1,T2 p2,...,Tn pn)it means
meth
is the method's name,
R
is the method's return type, and there are
n
parameters where pi
is the
name of the i
th parameter and Ti
its
declared type. Then a call takes the form
meth(a1,a2,...,an)
where each
ai
is an expression which evaluates to a value of type
Ti
. When the method call is evaluated, the code which
forms the body of the method is evaluated in an environment which consist
of variables p1
, p2
up to pn
where each pi
is initialised to the value ai
from the method call. Additional local variables may be added to the
environment as variable declarations inside the code for the method are executed.
The call may be used in any place where a value of type R
is required. If the method call is executed as a statement on its own
then the return value is ignored or there is no return value if
R
is void
, in which case the method
call will be intended to have an effect by changing a mutable object passed
as an argument or by performing some external action.
Static methods are self-contained: all the data they use is passed to them
as arguments (this ignores variables declared as static
otherwise
known as "class variables", but we will not consider these here). Methods
which are not declared as static (so they are "object methods") must be
"called on" a reference to an object of the class the method is defined in.
In most cases this means the method call takes the form
v.meth(a1,a2,...,an)
where v
is a variable name of that class, but the call could also be attached
to another method call which returns a value of the type of that class.
The method is defined in the class with the form
R meth(T1 p1,T2 p2,...,Tn) { ... code ... }and execution of the code in the method works similarly to execution of the code in a static method, with an environment which contains variables
p1
, p2
up to pn
with each pi
initalised to the matching ai
of the method call. However, the environment of the method call also
contains the variables from the object it is called on. These are the
variables which came into existence when the object was created and remain
in existence until the object disappears when no other object references it.
If these variables are changed to refer to something else during the
execution of the code in the method, then that change is permanent, it
remains in effect after the method call is finished. So that is how a
method call on an object can change the state of that object. Like
a static method call, an object method call where the return type of
the method is R
can be used in any place where a
value of type R
is required.
Note that if a call to a further object method is made in the code
of an object method it does not have to be indicated as attached to
any particular object. If it is not, it is taken to be attached to the
same object that the method it is called in is attached to. If a method
is intended to be called only by other methods in the same
class then it can be declared as private which means
any attempt to use it in another class will be indicated as an error
by the Java compiler. This is done by putting the word private
in front of the rest of the method. Using the word public
in the same way means the method can be accessed in all other Jave classes.
If a method is neither declared as public
nor private
it can be accessed by other classes, but only in the same package. But as
we haven't discussed defining our own Java packages, you can ignore that
possibility, and in general should declare methods as either public
or private
.
MyObject
and inside it
(but not inside a method) declarations Type1 var1
and Type2 var2
it means that every object of
type MyObject
has its own variable called
var1
which is of type Type1
and its own variable
called var2
which is of type Type2
.
If obj
is a variable of type MyObject
,
then the variable var1
of the object referred to by
obj
can be referred to by obj.var1
and so
on for other variables of the object. However, object variables are
very often declared as private
. As with private
methods, this means they can only be referred to in the code of methods inside
the class where they are declared.
It is possible to declare a variable in an object as public
meaning code in any other class can access it directly in the form
obj.var1
where obj
is an object reference and
var1
a variable name. However, it is considered good
practice not to have any public
variables in a class.
Making all object variables private
means that an object
controls its own state, the values of its variables can only be altered by
its own methods.
A constructor of a class is written like a method in the
class, but its name must be the same as the class name and it
has no return type. So a constructor for objects of the type
MyObject
will have the form
MyObject(T1 p1,T2 p2,...,Tn an) { ... code ... }A call to it takes the form
new MyObject(a1,a2,...,an)
and it can be used wherever a value of type MyObject
is
required. So note the word new
is always used when a
new object is created, but not in any other circumstances.
The code of the constructor is executed like a method, so in
an environment with local variables p1
to pn
where each variable pi
is initialised to ai
,
with the addition of the variables of the object. This is when those
variables come into existence and they remain in existence so long as
the object constructed remains in existence. In most cases, a constructor
will do nothing more than assign values to object variables. Note that
any object variable which is not assigned a value in the constructor
will be initialised to the value 0
if it is numerical,
false
if it is boolean, and null
for any
object type. Constructors can also be declared as either public
or private
, though at this stage you may wonder what use is
a private constructor.
DrinksMachine
. At that point you were
not shown the code that is inside the file
DrinksMachine.java,
you were only given the
methods of the class.
The full code is now given below (note this is slightly different from
the code in the files, the reason why will be explained later):
import java.util.ArrayList; class DrinksMachine { private ArrayList<Can> cokes, fantas; private int price,balance,cash; public DrinksMachine(int p) { price = p; balance = 0; cash = 0; cokes = new ArrayList<Can>(); fantas = new ArrayList<Can>(); } public DrinksMachine(int p,int c, int f) { this(p); for(int i=0; i<c; i++) loadCoke(new Can("coke")); for(int i=0; i<f; i++) loadFanta(new Can("fanta")); } public void insert(int n) { balance=balance+n; } public int getBalance() { return balance; } public int collectCash() { int oldCash = cash; cash = 0; return oldCash; } public int getPrice() { return price; } public int pressChange() { int change=balance; balance=0; return change; } public Can pressCoke() { if(cokes.size()>0&&balance>=price) { Can can = cokes.get(0); cokes.remove(0); balance=balance-price; cash=cash+price; return can; } else return null; } public Can pressFanta() { if(fantas.size()>0&&balance>=price) { Can can = fantas.get(0); fantas.remove(0); balance=balance-price; cash=cash+price; return can; } else return null; } public void loadCoke(Can can) { cokes.add(can); } public void loadFanta(Can can) { fantas.add(can); } public boolean cokesEmpty() { return cokes.size()==0; } public boolean fantasEmpty() { return fantas.size()==0; } public void setPrice(int p) { price = p; } }Here the lines
private ArrayList<Can> cokes, fantas; private int price,balance,cash;indicate that an object of type
DrinksMachine
has
five variables inside it, cokes
and fantas
of type ArrayList<Can>
(that is, an arrayList of
Can
objects) and price
, balance
and cash
of type int
. So when any non-static
method from class DrinksMachine
is executed (and there
aren't any static methods), it will be in an environment which has the
parameters to the method as variables along with the variables
cokes
, fantas
, price
,
balance
and cash
. In a constructor these
will be new variables, in any other method they will be the variables
from the object the method is called on. A variable with the name
this
is also provided in the environment for any non-static
method, it is set to refer to the object the method is called on, or to
the object being created in a constructor.
The lines
public DrinksMachine(int p) { price = p; balance = 0; cash = 0; cokes = new ArrayList<Can>(); fantas = new ArrayList<Can>(); }are a constructor for
DrinksMachine
. They mean that the
call new DrinksMachine(expr)
where
expr
is an expression which evaluates to an
integer value returns a reference to a new DrinksMachine
object, which has internal variables balance
and
cash
set to 0
, internal variables
cokes
and fantas
set to an arrayList of
Can
objects of size 0, and internal variable price
set to the value of expression expr
. Note that the
lines
balance = 0; cash = 0;are not strictly necessary, since if they weren't there these variables would be set to
0
as the default anyway.
The lines
public DrinksMachine(int p,int c, int f) { this(p); for(int i=0; i<c; i++) loadCoke(new Can("coke")); for(int i=0; i<f; i++) loadFanta(new Can("fanta")); }are a second constructor for class
DrinksMachine
. It is
permissible to have more than one constructor so long as they differ in
number and/or type of parameter. The two constructors mean that if
a call new DrinksMachine(...)
is made where
...
is a single integer expression, the first
constructor is used, if ...
is three integer expressions
separated by commas, the second constructor is used.
The first statement, this(p)
in the code of the second
constructor needs some explanation. It is another use of the
word this
than the one mentioned above. The word this
as a method call as the first statement of a constructor means that the
code from another constructor of the same class is used. In this case it
means that the code
for the first constructor with argument p
is used to set the
values of the internal variables of the new DrinksMachine
object before the rest of the code in the constructor is used to change
them. Here p
is the variable which holds the value of the
first argument to the constructor. Then the two loops cause methods
loadCoke
to be called c
times and method
loadFanta
to be called f
times where c
and f
are the variables in the local environment which hold the
values of the second and third arguments to the constructor. Note these are
examples of an object method call being in the class where the object method
is defined. As they are not specified as being called on any other
object they are called on the object being created by the constructor.
The methods in class DrinksMachine
are short and their effect should
be easily seen. The method written
public void insert(int n) { balance=balance+n; }causes the variable called
balance
of the
DrinksMachine
object it is called on to be increased
by the value of its argument. So, for example, if d
is a variable of type DrinksMachine
and m
is a variable of type int
, the call d.insert(m)
causes the statement balance=balance+n
to be executed
in an environment where m
is a local variable into
which the value of n
has been copied, but balance
means the actual variable called balance
in the object
referred to by d
. So this variable gets increased by
the value that is in m
in the environment where
d.insert(m)
was called.
The method written
public int getBalance() { return balance; }does nothing but return the value of the
balance
variable
of the DrinksMachine
object it is called on. Strictly
it means that when the call d.getBalance()
is executed,
the statement return balance
is executed in the environment
where balance
is the balance
variable of the
object referred to by d
.
The method written
public int pressChange() { int change=balance; balance=0; return change; }is an example of an object method with a local variable. When the call
d.pressChange()
is made, the local variable
change
comes into existence in the environment where
this method is executed, it is set to the value of balance
in this environment, and then balance
in this environment
is set to 0
. So the result is that change
holds the old value of the balance
variable of the
object referred to be d
, while that variable has its value
changed to 0
, this change persisting after the method has finished.
So this code enables the old value of balance
to be
returned and the value of balance
to be changed in one
method call. It is necessary to do it this way because a return
statement is always the last statement to be executed in a
method and causes the method to terminate. You could not, for example,
write the method as:
public int pressChange() // THIS IS DELIBERATELY SILLY CODE { return balance; balance=0; }because executing
return balance
would cause the
method call to terminate, and the following statement
balance=0
would never get executed.
The method written
public void loadCoke(Can can) { cokes.add(can); }shows an example of a method call on an object causing a method to be called on an object referred to by one of its variables. An object of type
DrinksMachine
has its own variable of type
ArrayList<Can>
called cokes
(and
another of the same type called fantas
). If c
is an expression of type Can
and d
is a
variable of type DrinksMachine
then d.loadCoke(c)
causes the call add(c)
to be made on the
ArrayList<Can>
object referred to by the variable
cokes
of the DrinksMachine
object referred
to by d
. The result is to add the object given by
c
to the end of one of the arrayList of Can
s
which forms part of the state of the DrinksMachine
object.
Note that "an expression of type Can
" is not the same
thing as a variable of type Can
. A variable of type
Can
is one example of an expression of type Can
,
but another example is a method call which returns a reference to an object
of type Can
. So new Can("fanta")
is an
expression of type Can
, as is d.pressCoke()
where
d
is of type DrinksMachine
. In the second
constructor for DrinksMachine
, the repeated call of
loadCoke(new Can("coke"))
means new Can
objects
are repeatedly created and added to the end of the arrayList referred
to by the variables cokes
in the object.
The method written
public Can pressCoke() { if(cokes.size()>0&&balance>=price) { Can can = cokes.get(0); cokes.remove(0); balance=balance-price; cash=cash+price; return can; } else return null; }shows a local variable, methods called on one of the arrayList objects which form part of the state of a
DrinksMachine
object,
and the values of integer variables which form part of the state being
changed. Remember it is meant to represent the operation of pressing
the button labelled "Coke" on a drinks machine after some money has been
inserted into the machine. The variable balance
which
forms part of the state of the object represents money inserted into the
machine, but not yet spent or collected after pressing the "Change"
button. The variable cash
represents the amount of money
spent on drinks in the machine since the cash was last collected (or
since the machine was created if it has never been collected). The
variable cokes
refers to an arrayList which represents
cans waiting to be delivered. In the code, cokes.remove(0)
causes the first can to be removed from the stored cans. Then
the price of a can is taken from the balance held and added to the
cash held. A reference to the first can was taken before it was removed
and held in the local variable can
, this is then returned
as the result of the method call.
Note the lines
Can can = cokes.get(0); cokes.remove(0);above could actually be written as
Can can = cokes.remove(0);This is because the
remove
method with int
argument
in class ArrayList
has a return value, the value of the item
removed when the method is called.
Often we don't need to keep that value, so we call remove
on
an arrayList without making any use of it. In the code given the operation
of getting the Can
object at the start of the arrayList and then
removing it from the arrayList is split into two parts to make it clearer.
The important thing about the methods in class DrinksMachine
is that
together they ensure a DrinksMachine
object correctly represents a
drinks machine. The only way the variables in a DrinksMachine
object can be changed are in ways which reflect how a real machine
would work. You cannot change the value of the variable cash
in a DrinksMachine
object except in a way which represents
buying a drink or collecting all the accumulated money spent on the machine.
The arrayLists which represent the cans stored in the machine can only
be changed in a way which represents taking a can from one end and adding
a can to the other.
Last modified: 15 February 2006