In your first programming course, you came to think of a Java program as something that starts:
class SomeName extends basic { public static void main(String[] args) { ...(the
...
is just to indicate this is just part of
the content of a file)
where SomeName
was the name you gave to the program.
Following this, you had some Java code, and then you ended with:
... } }where the first
}
is to balance the {
at the end of the line starting public
and the second
}
balances the {
at the end of the line
starting class
. Note that in Java it doesn't really
matter how you space out your code with blank characters and new
lines, it still means the same and behaves in the same way when compiled
and run, however it is done. However, for other people to understand
your code, it is important to lay it out in a way that makes its structure
clear. Some people (myself included) prefer a layout where the
opening {
occurs on a new line of its own, and its balancing
closing }
occurs below it. It is very important,
however, that you always think of {
and }
as forming a pair, when you have one should know where the other
matching one is. A good way of ensuring this when you are writing
programs in an editor is when you write {
immediately
put in the matching }
and then fill in what comes in between.
In most Java programs the Java code which fills in the
above is just a tiny part of the whole program. In your first programing
course, you looked at defining separate static methods in
the same class as the main
method, which is one way
of structuring programs by making what you thought of as separate
"mini-programs". Before object-oriented programming became common,
this was the main way of structuring programs. Now the main
way is defining separate classes, this became popular as programs
became longer and more complicated and additional ways of structuring
were necessary to make sense of them, and to design, construct and
modify them. It is always easier to deal with something complicated
if you can divide it into self-contained parts and look at parts as
separate things when necessary. The object-oriented approach to
programming first became popular through the programming language
C++ in the 1980s, although it originated in a programming language
called Simula in the 1960s, and was carried on in the 1970s
through a programming language called Smalltalk. Java was introduced
in the 1990s as a programming language which resembled C++ but
removed some of its complicated non-object-oriented aspects.
You will start to learn how to define separate classes in your
object-oriented programming course. In this course, however, I want
to start off by showing you how to use classes that have already been
defined. This illustrates an important aspect of object-oriented
programming: you can use code already written by someone else, you
don't have to write every aspect of your program yourself. In fact,
you can use other people's code without even knowing what it looks
like, so long as you know what the result of using it will be.
The drinks machine example is a simple case of this.
Using the Drinks Machine classes
In the directory
~mmh/DCS128/code/drinksmachinesyou will find three files:
Can.class
, DrinksMachine.class
and
EmptyCanException.class
which end in .class
but do not have a matching file ending in .java
.
Actually, there are matching files called
Can.java
, DrinksMachine.java
and
EmptyCanException.java
which I wrote and compiled
to produce these .class
files, but I have not made
these available to you. You can use the code
in these files without knowing what it is, so long as you know
what the public methods
are in these classes, and have
the .class
files. You will need to copy these three
.class
files into your own directory to use them.
To help you start out, there is also a file called
UseDrinksMachines1.java
in the directory
~mmh/DCS128/code/drinksmachines
. Copy
this, along with the three .class
files,
and compile it using the Linux command:
javac UseDrinksMachines1.javato produce the file
UseDrinksMachines1.class
. Then entering the Linux command
java UseDrinksMachines1will run the program.
Here is a complete listing of the code in the file
UseDrinksMachines1.java
:
class UseDrinksMachines1 { public static void main(String[] args) { DrinksMachine machine = new DrinksMachine(50,10,10); System.out.println("I insert 200p into the drinks machine"); machine.insert(200); System.out.println("I press the button labelled \"Coke\""); Can myCan = machine.pressCoke(); System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have "+myCan+" and "+myChange+"p"); } }
The code in the main
method represents putting a two
pounds coin into a drinks machine, pressing the button labelled
"Coke" and then pressing the button labelled "Change". The
result is to return a can of coke and 150p in change. If you
run the code, you will see statements printed saying this is what
is happening. However, the System.out.println
statements
just print commentary, it is the other statements which do the work:
DrinksMachine machine = new DrinksMachine(50,10,10); machine.insert(200); Can myCan = machine.pressCoke(); int myChange = machine.pressChange();If the code in your main method consisted of just the above, and you compiled and ran it, you would see nothing at all happen on the screen. But your computer will have done something which simulates putting money into a drinks machine, pressing buttons and getting results.
This illustrates an important aspect of computer programming - when you run a program, things happen that you can't see! In some ways, writing a computer program is like building a machine - you put things together to make bigger things, and you can make the machine you have put together run. But with a machine you have physical parts you can handle, and maybe a mechanism you can see working. With a computer program, it's all happening in the imagination. The only "real" thing that is happening, which you will have learnt about if you took a Computer Architecture course, is the transfer of tiny electrical charges, but you have to have the imagination to think of this in terms of what your program is meant to model. Being able to think through things happening when there is nothing there for you to handle or even to see is a crucial aspect of becoming a computer programmer.
Adding statements which print appropriate messages is one way of
keeping track of what is happening. However, you should not confuse
these statements with the statements that do the work. For example,
here machine.insert(200)
is the statement that has
the effect of inserting an imaginary two pound coin into an
imaginary drinks machine, while
System.out.println("I insert 200p into the drinks machine")
only has the effect of causing that particular message to be printed
on the screen.
As a very minor change from what you saw in your first programming course,
note the words extends basic
no longer occur after the
words public static void main(String[] args)
.
These were there to enable you to access some simple input/output
commands which are not already provided as a standard part of Java.
As we won't carry on using those commands in this course, you won't
see that extends basic
in our examples.
DrinksMachine
in terms of a real drinks machine,
which has buttons you can push, displays you can see, a slot you
can put money onto, a slot you can take change from, and a place
you can take a can of drink from, you are starting to use that
imagination which is necessary to become a computer programmer.
However, other things you may have to program may not have such
a close correspondence to "real world" objects. We have to devise
ways of thinking about them, perhaps involving diagrams or maybe
just thought exercises, which make these abstract objects more
real to us so that when we cause them to be manipulated by the
computer programs we write we have a feel for what is happening,
and can plan our programs to do useful work.
Most Java class files define a type of object. So the files
Can.class
, DrinksMachine.class
and
EmptyCanException.class
contain the compiled version
of code which enables the types Can
,
DrinksMachine
and EmptyCanException
to be used. You can use these types in just the same way you
have used the built-in types which Java has, such as int
and String
. So you can use them to define variables of
that type, or arguments to methods of that type. However, to use them,
their .class
files will have to be in the same Linux directory
as the .class
file of the code which uses them (this is a
simplification of a more complex situation, but for the purpose of this
course that is all you need to know). The type Can
is
used by objects of the type DrinksMachine
, and the type
EmptyCanException
is used by objects of the type
Can
. At this point you need not be concerned at what the
type EmptyCanException
is used for, since it is needed
for a more advanced aspect of Java we won't cover yet. However, you
need to have the file EmptyCanException.class
in your
directory in order to be able to use the type Can
.
In Java, you need to be careful to distinguish between a variable which refers to an object, and the object itself. A statement consisting of a type name followed by a variable name declares a variable of that name which has that type, you can also have a type name followed by several variable names separated by commas which declare several variables of that type. What this means is that that the names defined may be used to refer to objects of that type, but it does not cause an object of that type to be created. So:
DrinksMachine machine;means a variable called
machine
has been set up and may
be made to refer to objects of type DrinksMachine
, but
it doesn't cause any DrinksMachine
object to be created.
An expression consisting of the word new
followed by
the type name followed by some arguments within round brackets
(it may have zero arguments, but in that case the brackets must
still be there) creates a new object of that type. In order for that
object to be used, a variable must be set to refer to it. This can
be done by an assignment statement, so for example
machine = new DrinksMachine(50,10,10);creates a new
DrinksMachine
object and causes the
variable machine
to refer to it. It could also be
done (we shall see an example later) by making the new object
expression an argument to a method. The number of arguments between the
round brackets and their types depend on how the type has been defined,
below it is explained
that the expression here simulates the introduction of a new drinks machine
which charges 50p for its drinks and initially holds 10 cans of Coke and
10 cans of Fanta.
In Java it is permissible, and common practice, to combine the declaration of a variable with an assignment giving it a value. So the statement
DrinksMachine machine = new DrinksMachine(50,10,10);is a combination of the above two statements. Please remember, however that a type name followed by a variable name is only used when you want to declare a new variable; when you use the variable name after that you should not accompany it with the type name. Note also that variable names have a scope - they can be used only up to the closing
}
which matches the nearest opening
{
which occurred before the variable declaration.
You have seen static
methods in Java, which you have
learnt to think of as "mini-programs", but most methods in Java
classes are not static. A method which is not static must be called
attached to an object reference, we say it is "called on" that object.
The methods that may be called on a particular type of object are
specified in the class of that object. The statement
machine.insert(200);calls the method
insert
with the argument 200
on the object referred to by the variable machine
.
Since the method call is a statement on its own, it is likely to be a method
which has return type void
, and calling it has some sort
of effect which carries on after the method call is finished. In
this case, it represents inserting a two pounds coin (or 200p) into
the drinks machine, although the drinks machine does not return
anything it is changed internally and now stores the fact that this
amount of money has been put into it.
The statement
Can myCan = machine.pressCoke();is another combination of a variable declaration and an assignment which gives that variable a value. It is the equivalent of the two separate statements (a variable declaration followed by an assignment):
Can myCan; myCan = machine.pressCoke();A variable called
myCan
is declared of type Can
,
that is it can refer to objects of type Can
. This type is
defined in the file Can.java
which was compiled to get the
file Can.class
. This time, we do not directly create a
new Can
object, but obtain one by calling the method
pressCoke
on the object referred to by the variable
machine
. The method pressCoke
has zero
arguments, but note it is still necessary to include the opening and
closing brackets in the call even though there are no arguments. The
return type of the method pressCoke
must be Can
.
Calling this method represents pressing the button marked "Coke" on the
drinks machine. When this operation takes place, a can of Coke is delivered,
but also the internal state of the machine changes - it has one less can
of Coke stored internally, and also the amount of money it registers as
holding for you is reduced by the price of a can of Coke.
The statement
int myChange = machine.pressChange();is similar to the previous statement, this time declaring a variable of type
int
which is initialised to the value returned
from calling the zero-argument method pressChange
on
the object referred to by the variable machine
. This
represents pressing a button which causes the machine to return the
amount of money that has been inserted but has not been spent on buying
cans of drink.
DrinksMachine
objectsDrinksMachine
, and seen a very simple example of
their use, let us consider a full definition of their use. Here
is a list of all the public methods, given by their signatures,
in the DrinksMachine
class:
class DrinksMachine { DrinksMachine(int p) DrinksMachine(int p,int c, int f) void insert(int n) int getBalance() int getPrice() boolean cokesEmpty() boolean fantasEmpty() int pressChange() Can pressCoke() Can pressFanta() void loadCoke(Can can) void loadFanta(Can can) void setPrice(int p) int collectCash() }The full file
DrinksMachine.java
would contain
variable declarations which represent the internal state of the
machine, and code for each of the methods which is obeyed when
they are executed. However, these method signatures are all you
need know to be able to make use of DrinksMachine
objects. Just like a real machine, you don't need to know what
is inside and how it works to use it, all you need to know is how
to interact with it using the interface it presents to
the public, through its displays and buttons and slots. To fully
understand, you need an explanation of what each method will do.
The names of the methods and the types of their arguments and return
type will generally give a clue.
Java allows a method to be overloaded, which means the
same name may occur more than once in the methods of a class,
so long as each time it is used it has an argument list which differs
from other argument lists of the same named method, either by having
a different number of arguments or by having different types of arguments.
In the DrinksMachine
example, only the constructor is
overloaded. There is one constructor with a single int
argument, and another constructor with three int
arguments.
So to create a new DrinksMachine
, you must either call
new DrinksMachine(n)
or
new DrinksMachine(m,n,p)
where m
and n
and
p
are expressions which evaluate to a value of type
int
. This means they could be variables of type
int
, or integer literals, that is actual numbers,
or arithmetic expressions, or calls to methods which return values
of type int
. The idea is that a call
new DrinksMachine(m,n,p)
represents setting up a new drinks machine where a drink costs
m
and it is preloaded with
n
cans of Coke and p
cans of Fanta. The call new DrinksMachine(n)
represents setting up a new drinks machine which isn't preloaded
with any cans. Note that the simple drinks machine we are modelling
is one which is set up so that it only has buttons to dispense two varieties of
drinks, Coke and Fanta.
As we saw above, the method insert
with an argument
of type int
represents inserting a coin into the drinks
machine, where the arguments gives the value of the coin in pence.
If you want to know how much money you have inserted so far and not
used, you call the method getBalance
on the object
representing the drinks machine. You can think of this as looking
at a display showing the amount inserted on a real machine.
Calling this method returns a value of type int
. It is
very important to understand the difference between a method which
returns a value, and a method which displays a value in some
way - they are not the same thing at all! When a method returns a
value, it is something that is entirely internal to the program's
execution. It only makes sense if you do something with the value
returned, you could pass it as an argument to another method which
maybe displays it, as in:
System.out.println("I have "+machine.getBalance()+"p left");or which uses it in some other way, or you could assign it to a variable for use elsewhere later. It never makes sense, however, to call a method which does nothing but return a value as a statement on its own, as in:
machine.getBalance();The effect is to return the balance but do nothing with it, so nothing has been achieved. Don't assume, for example, that the Java execution mechanism will magically know you want the value to be printed. The Java execution mechanism will do just what it is meant to do and nothing else. Computers are not like human beings. When we human beings communicate with each other, if one person doesn't express something quite correctly, the other person will often try to use common sense to work out what was really meant. Computers don't have common sense like that.
The method getPrice
is similar to the method
getBalance
, except that it returns the price of
a can of drink rather than the current amount inserted.
The drinks machines we are modelling in this class are of a
simple design where there is just one price for all drinks,
we would need a different set of methods if we wanted to model
machines where different drinks had different prices. You can think
of calling this method as equivalent to looking at a display of the
price on a real machine. Two further methods, cokesEmpty
and fantasEmpty
also represent looking at displays on the
machine, in this case the signs that indicate that a particular drink
has run out. These return boolean values, rather than integers.
There is nothing in the public interface of a DrinksMachine
object which tells us how many cans of a particular drink it has unless
it has none, even though that figure may be represented internally in
the object. This is the same as a real drinks machine, where as you
can't look inside and there is just a display which lights up when
one variety of drink runs out, you can only tell it either has none
or some of that drink.
Note that none of the methods of class DrinksMachine
actually causes anything to be displayed. They return values which could
be passed to System.out.println
or to other methods
which could cause them to be displayed. This is good practice because
it is flexible. You should always make the code which causes things to
be displayed in the screen as separate as possible from the code which
represents things internally in the program. The code which displays
things is known as the user interface code (do not confuse
this use of the word "interface" with the use to mean the public methods
of a class which form its interface with the rest of a program). A
mark of a good computer programmer is the ability to separate things
out, so that if you wish to change one thing you know clearly where
that thing is and don't have to make changes throughout the program.
For simplicity in this course, our user interfaces will be entirely
text based. However, we know that modern computer programs tend to
have graphical user interfaces. If our DrinksMachine
class has no user interface code, then we can switch from using it with
a text-based user interface to using it with a graphical user interface
easily, it's just a matter of passing the values returned by methods
such as getBalance
, getPrice
, cokesEmpty
and fantasEmpty
to other methods which give a graphical
display rather than a text display.
The method pressChange
doesn't just return a value,
it also changes the internal state of the object it is called on.
It returns the same value that a call of getBalance
on the same object would give, but if another call of getBalance
is then made, it would return 0
. You could test this by
running a program whose main
method had the statements:
DrinksMachine machine = new DrinksMachine(50,10,10); System.out.println("I insert 200p into the drinks machine"); machine.insert(200); System.out.println("The balance display reads "+machine.getBalance()); System.out.println("I press the change button"); machine.pressChange(); System.out.println("The balance display reads "+machine.getBalance());Note that in this case, even though the method
pressChange
does have a return value, it can make sense to call it in a statement on
its own and not do anything with the return value, if it happens we
want the effect of changing the state of the object it is called on
but don't need to use the value that is returned. This is what is done
in the line
machine.pressChange();a value is returned by this call, but nothing is done with it. Note that the statement
System.out.println("The balance display reads "+machine.getBalance());occurs twice, but has a different effect when the second occurrence is executed than when the first occurrence is executed, demonstrating that the object referred to by the variable
machine
has been
changed. Also note that in this statement, the result of calling
the method getBalance
on the object referred to by
the variable machine
isn't put into an int
variable, but is joined directly to a string and the result passed to
a System.out.println
to be printed. In Java, if you join
anything to a string using +
, you get a new string
created by joining the original string to the string equivalent of
the thing.
The methods pressCoke
and pressFanta
have
the effect of returning a Can
object, and also of changing
the state of the machine the method is called on to remove the can
object returned from its internal state, and also of changing the
value returned by a call of getBalance
to take off the
price of a can of drink. They represent pressing a button labelled
"Coke" or "Fanta" to deliver the appropriate drink. At this point you
do not need to know what you can do with a Can
object, except
that its string equivalent will be either "can of Coke"
or
"can of Fanta"
as appropriate.
But what happens if you call pressCoke
or
pressFanta
when there are no drinks of the
appropriate sort left in the machine? (Of course, we
really mean imaginary drinks in our imaginary machine,
whose only real existence is as data stored in the computer!).
One way of dealing with this might be to "throw an exception",
which is an aspect of Java you will cover later. In this case,
however, the class DrinksMachine
has been
programmed so that if a DrinksMachine
object
represents a drinks machine with no Coke cans left, a call of
pressCoke
will return null
, and
similarly for the case where no Fanta cans are left. The methods
pressCoke
and pressFanta
have also
been programmed so that if they return null
the value
returned by a call of getBalance
will not change,
representing the requirement that if you are not served a drink
you should not be charged for one.
Any variable which may hold any type of object may also hold the special
value null
, and any method which returns any type
of object may also return null
. The value null
can be thought of as meaning "unset". You can test if a variable called
obj
is set to null
by the test
obj==null
. If when code is being executed an attempt
is made to call a method on a variable which has been set to
null
, then an exception of type NullPointerException
will be thrown.
The remaining methods in class DrinkMachine
represent
operations which the owner of a drinks machine might carry out.
To keep things simple, no distinction has been made between these
and the operations a customer would carry out on the machine, they
are all represented as public methods. The methods loadCoke
and loadFanta
represent filling up the machine with
new cans. They take a Can
object as their argument. So
to call them, you would need to know how to create a new Can
object. The class Can
has a constructor which takes
a string and produces a Can
object representing the drink
named by that string. So new Can("Coke")
produces
a new Can
object representing a can of Coke. Given this,
the following code fragment represents loading ten cans of Coke into the
machine referred to by variable machine
for(int i=0; i<10; i++) machine.loadCoke(new Can("Coke"));This is an example of creating an object and immediately passing it as an argument rather than assigning a variable to refer to it. Each time
new Can("Coke")
is called, a new
Can
object is created, so ten separate Can
objects will be created by the code above. Note, there is no check that
cans of the appropriate variety are loaded. So, for example, the following
code fragment:
for(int i=0; i<10; i++) machine.loadCoke(new Can("Fanta"));is permissible, and represents loading cans of Fanta into the Coke can dispenser. You could experiment with this by trying the following
main
method:
public static void main(String[] args) { DrinksMachine machine = new DrinksMachine(50); for(int i=0; i<10; i++) machine.loadCoke(new Can("Fanta")); System.out.println("I insert 200p into the drinks machine"); machine.insert(200); System.out.println("I press the button labelled \"Coke\""); Can myCan = machine.pressCoke(); System.out.println("I press the button labelled \"change\""); int myChange = machine.pressChange(); System.out.println("I have a "+myCan+" and "+myChange+"p"); }and you will see the code is such that a drinks machine loaded with Fanta cans into the Coke dispenser will indeed deliver cans of Fanta when the "Coke" button is pressed.
The method setPrice
takes an argument of type
int
representing a new price for drinks given in pence.
After this method has been called on a DrinksMachine
object
the price charged for a drink changes to the new price that was
given. Again, you could write some code to experiment with this and
show it working. The final method, collectCash
represents taking out the money that has been collected as drinks
have been sold. It returns a value of type int
,
representing the amount of money the machine has accumulated. When
a call of collectCash
is made on a DrinksMachine
object, this value is returned, but also the internal state of the
object is changed to represent the amount of money stored in the
machine going down to 0.
Separation of concerns: definition and use of a class
We have shown that a class can be used by a programmer without
the programmer knowing what is in the class. First of all, however,
let's distinguish between a class and an object. You can think
of the class itself as the design or "blueprint" for objects,
and objects as machines built according to that design. So the
actual Java class DrinksMachine
represents plans for
building a drinks machine and saying how it operates, while creating
a new object of type DrinksMachine
using a constructor
from the class represents building an actual machine of that design.
Objects interact through calls on the public methods defined in their
classes. We don't need to know what actually happens inside the
objects when methods are called on them, but we do need to be sure
of what the results will be. In the drinks machine example, we had
an intuitive feel for what objects of the class DrinksMachine
should do, because they were modeling something familiar from the
real world. We described the behaviour of these objects informally
using English, but if we wanted to be a bit more precise about it,
we could use some logical notation which says exactly what effect a
call of a particular method should have. That is the way classes
ought to be written - we start off first with a description or
specification of how objects of that class should behave in
terms of what happens when various methods are called on them, then
we write the code for the class so that it works that way. Or we
might write the specification for the class, and then give it to
another programmer to write the code for the class. That is a good
division of work. We don't need to worry about what is inside the
class so long as it works to its specification, the programmer
writing the class doesn't need to worry about what the programs that
use it do, so long as it works to its specification.
Last modified: 17 January 2006