Algorithms and Data Structures in an Object-Oriented Framework (“ADSOOF”)
Using objects in Java: the drinks machines examples, part 1
Java is an object-oriented language. This means that when a typical Java program executes, objects are created and interact with each other by calling methods. A typical Java program will consist of large numbers of files, each of which contains a Java class. A class defines a type of object in terms of what makes up the object and how it can interact with other objects. Java programs may also include files containing interfaces rather than classes, which we discuss later(1). Java programs will also make use of classes and interfaces that are provided with the Java language rather than as files which form the program, we discuss Java's built-in classes later.
When you were first introduced to programming, you probably came to think of programs as a series of instructions, structured with loops and conditional expressions, but all in one file (as it is presented in the module ECS401 Procedural Programming). This module will start off assuming you are at this level, though most people taking it will have gone further, so the early material should be seen partly as revision. The module will move from this towards using the object-oriented features of Java, which is really what the language is all about. Most people taking this module will have had an introduction to the object oriented features of Java through the module ECS414 Object Oriented Programming.
The module will go through introducing object oriented programming in Java as if you have not covered it before, to ensure that everyone taking it is familiar with the important principles. If you were uncertain about how OOP works the first time you covered it, I hope you will find this particularly helpful, but even if you did well before please don't be tempted to skip this section as it does look at things in a different way and moves into new areas.
A simple Java program starts:
class SomeName { 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, there is some Java code, and then it ends 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. If you are writing code in an ordinary text editor, a
good way of ensuring this is when you write {
, immediately
put in the matching }
and then fill in what comes in between.
It is possible you are using a more sophisticated editor or interactive
development environment (IDE) which does some of the organisational
work like this for you, for example BlueJ
which was used in
ECS414 Object Oriented Programming,
or Eclipse,
or Netbeans which you will be using in
ECS505 Software Engineering. You are welcome to use any such environment you find helpful,
but in order to concentrate on the basics, this module will not assume you are using one.
In most Java programs the Java code which fills in the
above is just a tiny part of the whole program. Your introductory
programming experience will have introduced you to
the use of “procedures” and “functions”, which are the same sort of thing Java
refers to as static methods. These can be put into
the same class as the main
method, which is one way
of structuring programs by making what you might think 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.
Later in this module, we will cover writing our own separate classes
to define objects. In fact, most Java programs will consist of a large number
of separate classes, each in separate files. The class with the method called
main
is really just the starting point.
At this point, 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.
Java classes are written in files which end with the .java
prefix, the name of the file should be the same name as the Java class it
contains with .java
added to it.
If you are running your program through a Linux window, the command
javac
followed by the name of the file (including the
.java
prefix) with the main
method in it will
cause the program to be compiled. It will produce a file with the same name
as your .java
file, but with the prefix .class
.
Actually, it will also (without you having to mention those classes
in the Linux command) produce .class
files for any other
classes you have defined in separate .java
files which
are used by the code in the original file. Then,
to run the program you have to have the .class
files
in the directory you are running it in, and enter the command
java
followed by the class name of the class with
the main
method, without any prefix.
In the code folder for this section of the module,
you 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
.
You can use Java code without knowing what it is, so long as you know
what the public methods are in its classes, and have
the .class
files. You will need to copy these three
.class
files into your own directory to use them.
Actually, there are matching files called
Can.java
, DrinksMachine.java
and
EmptyCanException.java
which I wrote and compiled
to produce these .class
files, we shall see them later,
but it is important as an exercise to get the feel for using an object
without knowing exactly what is going on inside it. If the
development environment you are using does not have an easy way to use
.class
files without the accompanying .java
file,
this work will need to be done in plain Linux.
To help you start out, there is also a file called
UseDrinksMachines1.java
in the code folder
for the section. In this case, and in most other Java code you will use, you are given the .java
file.
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 the Computer Systems module, 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.
Another way of keeping track is to use the facilities provided by an IDE if you are using one. IDEs usually provided debugging tools, which enable you to set breakpoints. When you run the program, it will halt when it reaches the breakpoint, and there will then be facilities to view the values of the variables at that point and then resume computation.
The drinks machine example has been chosen deliberately to help
you visualise what is happening in a simple computer program
involving objects. It defines a class of objects which simulates
real objects with simple and predictable behaviour we know about
from the world we live and move in. If you are able to think about
what is happening when methods are called on an object of class
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
module 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, you could just accept you
need to have the file EmptyCanException.class
in your
directory in order to be able to use the type Can
. Actually
it is because one of the methods in class Can
throws an
exception of that type. We say more about exceptions
later, but as EmptyCanException
has been made a “run time exception”, you don't have to write special
code to deal with the possibility of an exception of that type being thrown.
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.
You can use any name you like for a Java variable, so long as it fits
into the rules for Java names, but it helps make your programs easier to
understand if you use variable names which relate to the purpose the variable
was introduced for.
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 may have seen static
methods in Java (or
“procedures”, “functions” or “subroutines" in other languages), and
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
objects
Now we have introduced the concept of objects of type
DrinksMachine
, 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
(“signature” is the official Java term, though the word “header” is
often used instead), 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, which is what is given in the signature) will generally give a clue.
The first two methods in the class are its constructors, although
constructors are often considered to be a separate sort of thing
from methods. Calling a
constructor always causes an entirely new object of its
class to be created. A constructor has the same name as the class,
and does not have a return type. All other methods must have a
return type, even if it is void
which is used for the
return type of methods that don't return anything. As we saw above,
a constructor is called by using the word new
followed
by the class name and the arguments. The word new
is
only used for calling constructors in Java (and for creating new
arrays, which could be considered a special form of constructor),
it is not used when any other method is called, and because it has
this special use you may not use the word new
as the
name of a variable or a method in a Java class.
Java allows a method to be overloaded2, 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 module, 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!).
For our simple examples 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.
However, you should not take the methods pressCoke
and
pressFanta
returning null
as an example you
should follow. For simplicity, in this set of notes we avoid the issue of exceptions in
order to concentrate on basic use of objects: calling methods on
them, passing them as arguments to other methods and so on. The problem is that if a method
can return null
rather than a reference to an object, the code that uses that
method may not check for that. For example, in the code we have given here, such as
UseDrinksMachines1.java
,
there is no special check that a call to the methods pressCoke
or pressFanta
has returned
null
rather than a reference to a Can
object.
Without such a check, a variable that should be set to the value returned by the method gets set to
null
. In a bigger program, that could cause an error later on, with a
NullPointerException
being thrown in some other place where that variable
is accessed.
As a general rule, always use throwing an exception rather than returning null
to indicate a method not being able to return a normal value. So a better way of dealing
with the issue of pressing a button when there are no drinks available from that button
would be to throw an exception. There are some notes which discuss Java's exception mechanism,
demonstrating it with a modified version of the Drinks Machine example
here.
If a method call throws a checked exception, code must always be written which deals with that
exception. This makes sure the issue is dealt with when it occurs, rather than being
forgotten and then causing problems later.
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.
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.
When you first learn about object-oriented programming, you tend to be shown and
write code which both defines a class and uses objects of that class at the same
time. The problem with this is that it means you may still think of a program in terms
of lines of code, and not grasp the crucial importance of a clear separation between code
that defines an object (its class) and code that uses objects of that class.
So that is why here you are deliberately being shown use of objects of the
DrinksMachine
class without being shown the code inside that class.
Also, in this set of notes, you are still thinking of a program as instructions
inside a main
method. Java programming mainly, however,
consists of writing the classes which define objects which are used in
other classes. The main
method just exists just to start things off
when code is run directly from a command line, it is not necessarily the biggest or most
important part of a program. In this module to run examples we need something
termed a “front-end” or “support code” to start things up, and a main
method
is a simple way to do it. However, the code in the main
method is not
what the module is about. As we are concentrating on internal code, we are keeping to
simple text-based interaction run from the command line. Programs for real use would, of
course, most likely have some sort of graphical user interface, but we keep out of
discussing that in this module, because code that interacts directly with human users is
not what the module is about.
The point here is to help you move away from the introductory programming mentality, where you think of programming as writing one piece of code which just interacts with you, so you are both the “user” and the programmer, towards a more professional approach where you think of programming as writing pieces of code that are just parts of a larger system.
(1) There is a third type of Java file, which is one which defines an enum
type. These are not covered in this module, but you can find details on them
here.
(2) Do not confuse overloading with overriding. Overloading means two methods in the same class with the same name but different headers. Overriding is an aspect of inheritance, which we will cover later, it means a method in a subclass which has the same header as in a superclass.
Last modified: 25 February 2019