For example, suppose we have a variable a
of type
int[]
which refers to the array we can show
diagrammatically as:
and we want to change it by adding the number 17
to the
end so it refers to the array we can show diagrammatically as:
In more general terms we want an operation that takes an integer and an array of integers and adds the integer to the end of the array. When writing code and thinking about writing code, we should always try and put it in general terms. If you have a particular problem, see if it is an example of a more general problem. Then code to solve the more general problem can be used to solve the specific problem by setting variables to the specific values of the problem.
If the array a
refers to the first array above,
we might wonder if the statement
a[8]=17;would do the trick. To generalise this, suppose
a
refers to the array, and n
holds an integer. We
know the components of the array are indexed from 0
to one less than the length of the array which is given by
a.length-1
, so would referring to a[a.length]
automatically add an extra component to the end of the array? We
could do this by the code fragment:
a[a.length]=n;and we could make this code fragment into a separate method we could call whenever we needed to add an integer to the end of an array of integers:
public static void add(int[] a,int n) // THIS IS SILLY CODE! { a[a.length]=n; }It would not work, in Java referring to an array cell beyond the size of the array causes a run time error. Technically this means it throws an exception, which in this case is of the type
ArrayIndexOutOfBoundsException
. Note that even
though you can tell from the code (if you knew how Java works) that
it could not possibly execute as intended, it is a run-time error,
it would not be picked up when the .java
file is
compiled, only when the .class
file is run.
You also cannot get round this by re-assigning a value to
a.length
. For example
a.length=a.length+1; a[a.length-1]=n;This time, this is a compile time error. When you attempt to compile the
.java
file to produce the .class
file you get an error message and the .class
file is
never produced. The technical reason is that a.length
counts as a final
variable, and final variables
cannot be given new values in assignments.
The reason why referring to a[a.length]
is a run-time
error not a compile time error is that when Java code is compiled
and something of the form a[...]
is encountered, the
compiler only tests that the ...
part is something
that is of type int
. It doesn't check to see if it is
the case that the code is such that the ...
cannot
possibly evaluate to a value which is greater than or equal to
0
and less than a.length
.
To generalise further, as in Java it is not possible to change the size of an array while the code is executing, destructive methods, which work by changing an array, can only be employed if the the desired change is to the contents but not to the size of the array.
A constructive method which has the effect of adding something to
the end of an array can easily be written:
public static int[] add(int[] a,int n)
{
int[] b = new int[a.length+1];
for(int i=0; i<a.length; i++)
b[i]=a[i];
b[a.length]=n;
return b;
}
With this, a=add(a,n)
will have the desired
effect of changing a
to refer to an array
which is like the old a
with n
added to the end. There is, of course, no requirement that
the variable names used in the method call correspond to the
variable names used in the method declaration, so equally
c=add(c,m)
, assuming c
is of type
int[]
and m
is of type int
,
will have the effect of changing c
to refer to an array
which is like the old c
with m
added to the end. But note calling this constructive method in
this way has its effect by creating a whole new array and changing
the original array variable to refer to it, it does not actually
change the old array object itself.
Don't think that something like:
public static void add(int[] a,int n) // THIS IS SILLY CODE! { int[] b = new int[a.length+1]; for(int i=0; i<a.length; i++) b[i]=a[i]; b[a.length]=n; a=b; }will work so that a call on its own
add(a,n);will have the effect of changing
a
to add n
to the end. You will find it has no effect at all. You may think
"but changes to arrays passed into a method are made permanently,
and a
is changed inside this method". Changes to the
array referred to by a
are carried on outside the method,
but if a
is changed to refer to another array
inside the method, which is what happens here, that has no effect on
a
outside the method. The reason for this is that
a
inside the method and a
outside the
method are separate variables which are aliases to the same array.
Sounds tricky, perhaps, so lets's look at it in terms of the sort
of diagrams we used before. Here is the situation just after the
method call add(a,n)
, using our first example values:
Now you can see here that since the two
a
variables
are aliases for the one array object, changing a[i]
inside the method will cause a change to a[i]
outside the method. But if a
inside the method is
assigned to refer to the array b
inside the method
refers to, which is the case with the a=b
here,
that will have no effect on a
outside the method.
This can be shown by the diagram below which represents the
situation when the method above is just about to terminate
after having executed a=b
.
When the method terminates, its variables
a
and
b
(and n
) go out of scope, and since
no variable then refers to the array which its a
and
b
were referring to, that array is "garbage collected".
The variable a
outside the method continues to refer
to what it referred to before the method was called. It is not
possible to have a method which takes a variable as an argument and
causes that variable to refer to a different object. It is only
possible for a method to change the content of an object to which
a variable it takes as an argument is referring.
If our remove operation works so that it always removes the last item of the array in this case we would get:
We cannot have a destructive method which does this because it involves a change to the size of the array, but we can do it with a constructive method:
public static int[] remove(int[] a) { int[] b = new int[a.length-1]; for(int i=0; i<a.length-1; i++) b[i]=a[i]; return b; }As before, we create an array of the size needed, and copy the items in. So with this, a call:
a2 = remove(a1);(assuming
a1
and a2
are declared as of
type int[]
) will cause a2
to refer
to an array with the length one less than the one a1
refers to and with the same integers in it except the last one.
A call
a1 = remove(a1);will appear to have the effect of removing the last integer from the array referred to by
a1
, but you should be aware
it does that by creating a new array and making a1
refer to it, if another variable was an alias for a1
that variable will continue to refer to the old array with its last
item in place.
Note that Java allows arrays of length 0, so the above remove
method would work fine if its argument were an array of length 1,
it would just return an array of length 0. If it were actually
passed a reference to an array of length 0 as its argument, however,
there would be a problem - you can't create an array of length -1
(attempting to do so causes an exception of type
NegativeArraySizeException
to be thrown).
A full specification for a method will state any cases where there are
valid arguments that would cause the method to throw an exception.
How to deal with an array of length 0 is a good example of a case
it's easy to forget could be possible. Also, an array of length zero
is not the same thing as null
, and it's possible for any argument
of an array type or any object type to have the value null
We might, however, want to specify the item we are removing
from an array. Suppose the variable a
refers to the
same example array we used before, and we want remove(a,12)
to change the array to:
or in general remove(a,n)
to remove from the array referenced by
a
the integer n
. A naive approach to this
might be to find where n
is stored and replace it by 0.
This could be done by the destructive method
public static void remove(int[] a, int n) // THIS IS SILLY CODE! { int i=0; for(; i<a.length(); i++) if(a[i]==n) break; if(i<a.length) a[i]=0; }The code is similar to the code we looked at previously for finding the position of an integer in an array of integers, but in this case having found the position we set the integer in that position to 0. We have a loop which increments a loop index variable and exits either when it indexes the value we are searching for, or it has gone through the whole array and not found it. Only if it exits if it found the position do we assign
0
to that position, so the effect is that our remove
method does nothing if the integer to be removed does not occur in the array -
again, what happens in this circumstance is something we should have
specified when we started the problem.
This code would result in
for our example, which is not the same as what was required. It is not possible to cause a component of an array to disappear by assigning it any sort of special value, as we noted before once an array is created its size is fixed, you can change what it stores but not how many things it stores.
So we have no choice but to implement our method constructively, since what we want is for it to return an array of length one less than the argument array. But as we saw above, there is the alternative possibility that the integer being removed does not occur in the array. In this case, we need to return a copy of the original array. Why a copy? Because if we execute
b = remove(a,n);and we have said that it works constructively, we don't want it to result in
b
unexpectedly being an alias of a
in the case where n
happens not to occur in the array
referenced by a
. If this did happen, later we might
change the array referred to by b
and not realise
this would cause a change to the array referred to by a
.
So we need a method which returns an array whose size is either one less
than the size of the argument array, or the same size, but we don't know
the size until we have checked whether the integer to be removed
occurs in the array or not. This leads to the following code:
public static int[] remove(int[] a, int n)
// Returns a new array which is the result of removing the
// lowest indexed occurrence of integer n from array a, moving
// all subsequent integers down one place in the array.
// Returns a copy of array a if n does not occur in it.
{
int i=0;
for(; i<a.length; i++)
if(a[i]==n)
break;
if(i<a.length)
{
// This is the case where i gives the position of integer n in array a
int[] b = new int[a.length-1];
for(int j=0; j<i; j++)
b[j]=a[j];
for(int j=i+1; j<a.length; j++)
b[j-1]=a[j];
return b;
}
else
{
// This is the case where integer n does not occur in array a
int[] b = new int[a.length];
for(i=0; i<a.length; i++)
b[i]=a[i];
return b;
}
}
Here we have given full comments to explain exactly what this method
does. This is good practice and should generally be observed, at
least when the code gets long enough so that it is not obvious just
from looking at it what it does. The comment at the top of the method is
for the benefit of someone who may want to use it. It doesn't say
how the method works because someone who just wants to use it isn't interested
in that, they would just want to know exactly what it returns. It
explains that it removes an integer from an array of integers by
moving all following integers one place down. You may think this is
obvious, but as we have noted above, it needs to be spelt out to make
sure it is quite clear this is exactly what is done.
The comments inside the body of the method are for the benefit of
someone who is trying to work out how the code actually provides the
desired result. They point out the blocks of code which deal with
the two possible alternatives. Note that in the case where n
is removed from the array, everything before the position where n
was is copied into the same position in the new array, but everything
after it is copied into the position indexed by one less, thus
accomplishing the "moving down" effect.
The comment describing what the method does makes clear that it only removes one occurrence of the integer argument, and that it's the "lowest indexed" one. There can be no doubt about what this means, which is good. An alternative interpretation of the "remove" operation might be to remove all occurrences of the integer argument in the case where it occurs several times. So if the array argument referenced the array
and the integer argument was 6
, the method would
return a reference to the array:
In this case we could first find the size of the new array to be returned, which is the length of the argument array less the number of times the argument integer occurs in the array. We then create the new array of the appropriate length and copy integers into it from the argument array. This gives the following code:
public static int[] removeAll(int[] a,int n) // Returns the array resulting from removing all occurrences of // integer n from array a, keeping the remaining contents of the // array in the same order. { int count=0; for(int i=0; i<a.length; i++) if(a[i]==n) count++; // Here count gives the number of times n occurs in a int[] b = new int[a.length-count]; for(int i=0, j=0; i<a.length; i++) if(a[i]!=n) { b[j]=a[i]; j++; } return b; }Again, there is a comment at the top describing the method in terms of what it produces, not how it does it, but also a comment inside the method to help understand how it does it. We have called the method
removeAll
to distinguish it from the method
remove
given previously. Note how when copying
from the argument array (referenced by variable a
in the
method) to the new array (referenced by variable b
in the
method) there are two variables used to index the array,
i
used to index a
and j
used
to index b
. This is because the integers do not go into
the same position in b
as they were in a
.
The variable j
is increased by one only when an integer
is copied from a
into b
so that j
gives the position of the next integer to be copied. This means that
j
is not increased by one when a[i]
is equal
to n
.
Another thing you can note in the code for removeAll
is
that it gives an example of more than one variable being declared in
the initialisation part of a for-loop. The variable j
is
declared there alongside the variable i
, but j
does not appear in the update part of the for-loop because it is not changed
every time the loop body is executed.
Another way of interpreting the general idea of a method which removes an integer from an array of integers would be for the argument to the method to be not the integer that is being removed but the position of the integer that is to be removed. So if the array argument to the method was the array as before:
and the integer argument was the number
2
the method
would return a reference to the array
where the integer indexed by 2
is removed, and all
integers after it are moved down one place. Remember that in Java
indexing we start at 0
, so that means the third integer
in the array is removed, which happens to be the first occurrence
of 6
. Here is code to do this, and we have called the
method removePos
to distinguish it from the previous remove
methods:
public static int[] removePos(int[] a,int pos)
// Returns the array resulting from removing the integer
// indexed by pos from the array a, moving all subsequent
// integers down one place
{
int[] b = new int [a.length-1];
for(int i=0; i<pos; i++)
b[i]=a[i];
for(int i=pos+1; i<a.length; i++)
b[i-1]=a[i];
return b;
}
When writing code dealing with arrays of integers you should always have
it clear in your mind when an integer variable is intended to hold
data to go in the array, and when it is intended to hold an index
to the array. It helps to use the variable name pos
in the method above rather than n
as we used for the
integer argument previously, as the name pos
indicates clearly that it is intended to represent a position.
It is a common source of programming errors amongst inexperienced
programmers writing code dealing with indexed collections of integers
to use an integer variable inconsistently so that data and indexes
to data get confused.
Note, the above code for removePos
does not cover
every possibility. If the method is called with the second argument
set to an integer either equal or greater than the length of the
array of the first argument, or less than zero, running the code
will cause it to halt and throw an ArrayIndexOutOfBoundsException
.
Either we should accept this, in whcih case it would be sensible
to make a note of it in the comment which explains how it works:
// Throws an ArrayIndexOutOfBoundsException if pos is less than 0 or // greater than or equal to a.lengthor we should modify the code so that it gives us some reasonable behaviour in these cases (maybe return a copy of the original array), and document that in the comment describing the method.
To demonstrate use of the methods discussed here, you can look
at the files
UseArrays8.java
,
UseArrays9.java
,
UseArrays10.java
,
UseArrays11.java
and
UseArrays12.java
in the directory
~mmh/DCS128/code/arrays
. Each of these files contains one
of the methods given here, plus a main
method which exists
just to read in some example data and show the result of calling the
method which is to be demonstrated.
Last modified: 23 January 2006