Aliasing only has an effect if the internal state of objects may be
changed. The internal state of an object is changed by calling a
method on it which changes the state of the object. In the
DrinksMachine
example, we saw that several of the
methods that could be called on a DrinksMachine
object
changed its internal state. We can only tell that the internal
state of an object is changed by calling a method on it which
displays or returns a value. As an example, two of the methods in
class DrinksMachine
had signatures:
int getPrice() void setPrince(int p)The first method,
getPrice
gives the price which the
DrinksMachine
object it is
called on sells drinks for. It doesn't change the state of the
DrinksMachine
object, so if we have a DrinksMachine
object referred to by variable mach1
, every time we call
mach1.getPrice()
we will have the same integer value
returned so long as we never call on mach1
a method
which changes the state of the object it is called on.
The second method, setPrice
, changes the price which
the DrinksMachine
object it is called on sells drinks for.
So this does change the internal state of the object it is called on.
If we run the following code fragment we can see that:
DrinksMachine mach1 = new DrinksMachine(50); System.out.println("Here the price is "+mach1.getPrice()+"p"); mach1.setPrice(60); System.out.println("Now here the price is "+mach1.getPrice()+"p");When we call
mach1.getPrice()
the first time, we
get the value 50
returned, when we call it the second
time, after mach1.setPrice(60)
has been executed,
the value 60
is returned.
Aliasing means that when we execute an assignment statement, such
as mach2=mach1
, the variable on the left hand side of the
=
sign stops referring to what it referred to before and
starts referring to what the variable on the right hand side refers to.
The variable on the right hand side continues to refer to what it
referred to before, so the effect is that mach1
and
mach2
refer to the same object. Executing the following code
illustrates this:
DrinksMachine mach1 = new DrinksMachine(60); DrinksMachine mach2 = new DrinksMachine(50); System.out.println("Here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); mach2=mach1; System.out.println("Now here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p");At first, price 1 is 60p and price 2 is 50p. After the execution of
mach2=mach1
, price 1 is 60p and price 2 is 60p.
This isn't because any object has had its internal state changed,
rather it's because mach2
has been caused to stop
referring to the machine that charges 50p and start referring to the
machine that charges 60p. Note that executing mach2=mach1
doesn't change what any other variable refers to, even if it refers
to what mach2
referred to previously. This can be
illustrated by executing the following code:
DrinksMachine mach1 = new DrinksMachine(60); DrinksMachine mach2 = new DrinksMachine(50); DrinksMachine mach3 = mach2; System.out.println("Here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); System.out.println(" and price 3 is "+mach3.getPrice()+"p"); mach2=mach1; System.out.println("Now here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); System.out.println(" and price 3 is "+mach3.getPrice()+"p");Here,
mach3
is set to refer to the same object
that mach2
originally referred to, the one charging 50p.
It still refers to that machine even after mach2=mach1
causes mach2
to stop referring to the machine charging
50p and start referring to the machine charging 60p.
This can be illustrated diagramatically by the following diagrams.
Here is the situation before the execution of
mach2=mach1
Here is the situation after:
Variables are illustrated by square boxes with their name underneath,
objects by uneven shapes. A variable referring to an object is
indicated by an arrow coming from the box representing the variable,
and leading to the shape representing the object. Because in this
case all we are concerned with is the price a machine charges for
a drink, the shapes representing DrinksMachine
objects
are labelled with just this.
Thinking in terms of diagrams like this, we sometimes say that a
variable is pointing to an object rather than referring to
it, and we may describe a variable as a pointer. It can
be seen that executing mach2=mach1
causes mach2
to stop pointing to the object representing the machine which charges
50p and start pointing to the object representing the machine which
charges 60p, that being the machine that mach1
points
to at the time of execution of the assignment. It doesn't change what
mach3
points to, before and after the assignment
mach2=mach1
it points to the object representing the
machine which charges 50p, which mach2
pointed to before
but does not point to after.
We change the internal state of an object by calling a method which
changes the internal state on a variable which refers to that object.
Since the object itself is changed, even though it is changed by
a call on one variable, the same change may be observed by calling
a method on another variable which refers to the same object. This
can be illustrated by extending our previous code with a call on
setPrice
on either mach1
or mach2
.
Since they both refer to the same machine, whatever change is made
through one can be observed by calling getPrice
on the
other. So if we execute the code:
DrinksMachine mach1 = new DrinksMachine(60); DrinksMachine mach2 = new DrinksMachine(50); DrinksMachine mach3 = mach2; System.out.println("Here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); System.out.println(" and price 3 is "+mach3.getPrice()+"p"); mach2=mach1; System.out.println("Now here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); System.out.println(" and price 3 is "+mach3.getPrice()+"p"); mach2.setPrice(70); System.out.println("And here price 1 is "+mach1.getPrice()+"p"); System.out.println(" and price 2 is "+mach2.getPrice()+"p"); System.out.println(" and price 3 is "+mach3.getPrice()+"p");the final situation after the execution of
mach.setPrice(70)
is illustrated by:
and it can be seen that mach2.setPrice(70)
changes the
price of the machine referred to by mach1
and the
machine referred to by mach2
at that point to 70p,
because they are the same machine.
DrinksMachine
objects which both change the
state of the object they are called on and return a value.
Effect 3 covers methods that cause something to be displayed, but
includes also methods which read from the screen, methods which read
or write to files rather than the screen, and if we were looking at
more advanced aspects of Java we could include things like methods
which cause communication across networks.
Objects which are not immutable are called mutable. Effect 4
above will only work with arguments that are mutable objects. The
example you have seen previously is arrays passed as arguments to
methods, if the array is changed within the method, that change is
observed in the reference to the array outside the method. We know
that DrinksMachine
objects are mutable, so we can
illustrate effect 4 with a method which takes a DrinksMachine
object as an argument. Below is the code for an example program,
which you can also find in the file
UseDrinksMachine2.java
in the directory ~mmh/DCS128/code/drinksmachines
import java.util.Scanner;
class UseDrinksMachines2
{
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
DrinksMachine machine = new DrinksMachine(50,10,10);
System.out.print("Enter the sum of money you wish to spend on cokes: ");
int amount = input.nextInt();
int cans = spendOnCokes(amount,machine);
int change = machine.pressChange();
System.out.println("You have bought "+cans+" cans of coke");
System.out.println("You have "+change+"p left");
if(machine.cokesEmpty())
System.out.println("You have emptied the machine of cokes");
else
System.out.println("The machine still has some cokes left");
}
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;
}
}
Please note the first line is a Java import statement. Import
statements are needed to use the many prewritten classes which are provided
with the Java language. In this case, it enables us to use the class
Scanner
to input an integer. This is what we are using to
get input in the place of the commands obtained by the use of
extends basic
you had in your first programming course.
However, you need not be concerned about that at the moment, the main thing
here is the method called spendOnCokes
. This method
takes an int
value and a reference to a
DrinksMachine
object, and returns the number of
cans of Coke that can be bought from the drinks machine represented
by the object if the amount of money given by the int
argument is inserted. It does this, as you can see by the code for the
method, by calling the method insert
on the
DrinksMachine
object passed as an argument with the
int
value as the argument to insert
,
and then repeatedly calling pressCoke
on the object
until either the method cokeEmpty
does not return
false
, or the method getBalance
returns a value less than the method getPrice
.
In other words, either the machine runs out of Coke cans, or the
money left to spend is less than the price of a can. The changes
made to the machine in the method are carried on after the method
has finished executing, so for example if the machine is made empty
in the method, it is empty after the method. Note that although
the method pressCoke
returns a Can
object, since the code for the method does not make use of the
Can
objects returned in any way, the call to
pressCoke
within it occurs as a statement on its own, it
is called only to have the effect of changing the internal state of
the object referred to by the variable mach
which it
is called on.
spendOnCokes
above is declared as
static
. Unlike other methods, when a static
method is called, it is not attached to any object. If the
method is called in the same class where its code is, then to
call it you just give the method name, followed by an opening round
bracket, followed by the arguments you wish to use separated by
commas, followed by a closing round bracket. To call a static
method from another class, you must attach the class to the
class name, similar to the way a call to a non-static method is
attached to a variable name representing an object.
In our code above, the call to the method spendOnCokes
occurs in the statement:
int cans = spendOnCokes(amount,machine);When this is about to be executed, the environment consists of the variable called
args
which is the argument to the main
method, and the variables input
, machine
,
amount
and cans
. It doesn't contain
the variable change
because that isn't declared
until after the method call.
The signature for the method spendOnCokes
is:
public static int spendOnCokes(int sum,DrinksMachine mach)which is followed by the code for the method. Note that signatures have types for each of the parameters, and a return type, method calls do not have types given with the arguments. That is because a method signature can be thought of as declaring new variables which form the initial environment for executing the code of the method. So the code for
spendOnCokes
starts executing with its environment consisting of the variables
sum
and mach
. You can think of these
as being assigned the values which match to them in the method call,
it is as if:
sum=amount; mach=machine;were executed. Now you can see why, even though a method executes in its own environment, changes made to objects passed as parameters are carried on after the method finishes. Although
mach
and machine
are separate variables in separate
environments, because passing arguments to a method works like
assignment, mach
becomes an alias for the object
referred to in the old environment by machine
. The
situation is something like:
The variable count
is then added to the new environment
and set to 0
when the declaration int count=0
is executed.
Executing a return
statement in a method always has
the effect of halting the method and returning to the point where
the method was called (this is even if the return
statement is inside a loop, for example). You can think of the
return
statement as assigning to a temporary variable
the value that it returns, and then execution returns to the old
environment, but the method call that was executed is replaced
by this temporary variable. If we use the name return
for the temporary variable, in our example it is as if
return count
in the method was
return=count
, and then the variable called return
was made available in the old environment with the statement
int cans = spendOnCokes(amount,machine)
becoming
int cans = return
.
DrinksMachine
then this says that what you can do with it is call on it any
of the public methods (not including the constructors)
of the class DrinksMachine
, which we gave
previously, or
pass it as the nth argument to any method whose signature
declares that its nth argument must be of type
DrinksMachine
(the only case of this we have
seen so far is the method spendOnCokes
whose
second argument must be of type DrinksMachine
),
or you can assign it to another variable of type
DrinksMachine
.
You can also use a call to a method which has a particular return
type to call a further method of that return type on, or as an
argument to a method which requires that type. To demonstrate this,
let us consider a static method whose return type is
DrinksMachine
:
public static DrinksMachine cheaper(DrinksMachine m1,DrinksMachine m2)
{
if(m1.getPrice()<m2.getPrice())
return m1;
else
return m2;
}
A complete program showing this method with a main
method
to demonstrate its use can be found in the file
UseDrinksMachine3.java
.
Suppose we have two variables, mach1
and mach2
of type DrinksMachine
which have been set to refer
to DrinksMachine
objects. Suppose we want to get a
Coke from whichever is the cheaper machine, and we have two pound coins.
We could do that with:
mach1.insert(100); mach2.insert(100); DrinksMachine mach3 = cheaper(mach1,mach2); Can myCan = mach3.pressCoke(); int myChange = mach1.pressChange()+mach2.pressChange();Here the assignment
mach3 = cheaper(mach1,mach2)
causes mach3
to become an alias for either mach1
or mach2
, whichever has the cheapest price. So the code
fragment represents putting a pound coin into both machines, then finding
out which is the cheapest, pressing the "Coke" button on the cheapest,
and pressing the "change" button on both machines.
We could replace the above statements by:
mach1.insert(100); mach2.insert(100); Can c = cheaper(mach1,mach2).pressCoke(); int myChange = mach1.pressChange()+mach2.pressChange();calling
pressCoke
on whichever object is returned
from the call to cheaper(mach1,mach2)
without
assigning a separate variable for it. Sometimes this is a useful
shorthand to avoid declaring variables whose only use is to have a
method applied on them once.
In this case, however, what we did was a bit odd. We would normally
want to find the cheapest machine first, then put money in just
that one and press its "Coke" button and "change" button. In this
case, since we need to call three separate methods on the object
returned by cheaper(mach1,mach2)
, we have to give
it a separate name, so that the methods can be called on it in turn:
DrinksMachine mach3 = cheaper(mach1,mach2); mach3.insert(100); Can myCan = mach3.pressCoke(); int myChange() = mach3.pressChange();
Similarly, suppose we wanted to see how many cans of Coke we could get
with a two pounds coin put into the cheapest machine. This can be
done with the methods cheaper
and spendOnCokes
given above, as in:
DrinksMachine mach3 = cheaper(mach1,mach2); int numCans = spendOnCokes(200,mach3); System.out.println("I can buy "+numCans+" cans with two pounds");but if we have no further need to refer to the cheapest machine, we could put the call to the method
cheaper
directly
as the argument which requires its return type of the method
spendOnCokes
:
int numCans = spendOnCokes(200,cheaper(mach1,mach2)); System.out.println("I can buy "+numCans+" cans with two pounds");
So, as a general point, you can save on the declaration of a variable which is only used to hold the value of a method call and then to use that value just once by putting the method call directly in the place of that variable's use. This doesn't make any difference to how the program runs, it's just something that may make a program clearer to follow for people trying to read it and understand it. On the other hand, very complex expressions involving several method calls inside method calls may be hard to follow, and it may be helpful to break them into parts with variables used to hold the values of the parts.
Note that assignment statement must have a variable name on the left-hand side of the equals sign. It does not make sense, for example, to write:
cheaper(mach1,mach2) = mach3;it must always be:
mach3 = cheaper(mach1,mach2);
So the important thing here is that types tell you how to fit the
pieces of a program together: a non-static method call must be attached to a
reference to an object of a type which can take that method call,
an argument to a method call must be of a type which matches the
parameter in the same position in the method signature. The situation
is complicated by the way that one type in Java may be a sub-type
of another type, but we won't cover that yet.
Scope of objects
As we said, a variable remains in existence until it goes out
of scope, that is when execution goes past the closing }
which matches the opening {
of the block of statements it
is called in. When a method is called, execution enters the environment
of the method, so the variable is inaccessible, but it remains in
existence with the value it was given and becomes accessible again when
the method finishes. Note that even if the method that is called has
an argument or an internal variable of the same name as a variable
in the old environment, it is an entirely separate variable in the
new environment, it has no connection with the variable of the
same name in the old environment.
An object, however, remains in existence for as long as there is
some variable which refers to it. So an object which is created inside
a method call remains in existence after the method has finished
executing so long as the method call causes a variable in the old
environment to refer to it. The most common way in which this
can happen is for a reference to the object created in the method
to be returned as the result of the method. As an example,
consider the following program in the file
UseDrinksMachines4.java
:
import java.util.Scanner; class UseDrinksMachines4 { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("Enter the price for drinks on your machine: "); int p = input.nextInt(); DrinksMachine machine1 = new DrinksMachine(p); DrinksMachine machine2 = cheaperBy10p(machine1); System.out.println("The price you entered is "+p+"p"); System.out.println("Machine 1 charges "+machine1.getPrice()+"p"); System.out.println("Machine 2 charges "+machine2.getPrice()+"p"); } public static DrinksMachine cheaperBy10p(DrinksMachine mach1) { int p = mach1.getPrice()-10; DrinksMachine mach2 = new DrinksMachine(p); return mach2; } }The idea of the method
cheaperBy10p
is that it takes a
DrinksMachine
object as its argument, and returns as its
result a reference to a new DrinksMachine
object which it
creates whose price is 10p cheaper than the price of the
DrinksMachine
passed as its argument. The
situation with the environments as the execution of
the return
statement in the execution of the call
to the method cheaperBy10p
takes place is:
The parameter mach1
in the method cheaperBy10p
is matched against the argument machine1
in the call in
the main
method, so machine1
in the
environment for the main
method and mach1
in the environment for the cheaperBy10p
call refer to
the same object. A new DrinksMachine
object is
created in the cheaperBy10p
call, with mach2
set to refer to it. A reference to this object is returned as
the result of the call, and machine2
in the old
environment was set to the result of the call, so it is set to
refer to the object which mach2
refers to in the
new environment. When the call to cheaperBy10p
finishes
with this return
statement, the new environment
disappears, and execution returns to the main
method
in the old environment. But the object created in the call remains
in existence, because the variable machine2
refers to it.
The example above shows a case of the same variable name used in the old
environment and the new environment. The variable called p
in the new environment is a different variable from the one called
p
in the old environment. The code for cheaperBy10p
sets up its own variable called p
when it executes
its statement int p = mach1.getPrice()-10
.
The variable called p
in the old environment stays
with its value which was set by
int p = input.nextInt()
in the main
method. When execution returns to the
main
method after the execution of the call to the
method cheaperBy10p
, the variable name p
means this original variable called p
with the
value set by the call input.nextInt()
, it doesn't
get changed by the use of a local variable of the same name
in the method call.
In Java an object disappears if no variable refers to it.
This could happen if all the variables that refer to it go out
of scope, or if only one variable refers to the object and that
variable is then set to refer to another object. For example, suppose
an object is referred to by variable mach1
and by no
other variable. Then suppose mach1=mach2
is executed.
This causes mach1
to start referring to the variable
which mach2
refers to, so nothing refers to the object
it used to refer to, so that object disappears. In Java, the computer
storage used for an object which is no longer referred to by any
variable will be reused when new storage is required when a new object
is created. This is referred to as automatic garbage collection.
In some other programming languages it is necessary for the programmer
to put in a special command to cause computer storage to be
recycled in this way, otherwise after an object has been created its
store may never be re-used while the program is executing even
if no variable refers to it. In languages without automatic garbage
collection a program that creates lots of new objects during its
execution could cause the computer to run out of available store if
no instructions are written to allow store to be recycled.
Last modified: 10 June 2005