Figure 2.3 shows the code for a class StackArray that implements the Stack interface.
import java.util.NoSuchElementException;
public class StackArray implements Stack {
private Object[] stack;
private int top; // for storing next item
public StackArray() {
stack = new Object[1];
top = 0;
}
public boolean isEmpty() {
return top == 0;
}
public void push(Object item) {
if (top == stack.length) {
// expand the stack
Object[] newStack = new Object[2 * stack.length];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[top++] = item;
}
public Object pop() {
if (top == 0) {
throw new NoSuchElementException();
} else {
return stack[--top];
}
}
}
|
(top == stack.length) so we cannot write to stack[top]. In that case we expand the
stack by first creating a new array called newStack, twice the
size of the current stack array. The contents of the old array
are copied to the new array usingSystem.arraycopy(stack, 0, newStack, 0, stack.length);This is equivalent to
for (int i = 0; i < stack.length; i++) {
newStack[i] = stack[i];
}
but we have used the
arraycopy
method from the
System
class since it is likely to be more efficient. Finally reference to
newStack is assigned to the instance variable stack.
After this reassignment, the original array can no longer be addressed
and it is available to the garbage collector for recovery. We can
then proceed as before.
The only other novelty lies in the public constructor StackArray(). This now takes no arguments. Since we are able to expand the stack to fit the problem, there is no need for the client to specify the size. This implementation takes the smallest stack as default, namely a stack of size 1. It could be argued that there would be little use for such a small stack, and that the default should be 2, 4, 8 or whatever. That would be a fair modification, although it would involve a conventional decision about the default initial size. It would also be possible to provide another constructor taking an initial stack size as parameter; but we aim to keep things as simple as possible.
The idea of expanding the stack by a factor of two, when necessary, is
sensible. It is a rough adaptation to the scale of the particular
application. We want to get the size right to within an order of
magnitude. You could imagine someone asking for a ball-park figure:
``are we talking tens, hundreds, thousands or what?'' Scaling up by a
constant factor,
say, is the simplest expression of this idea. We
choose
but you could choose
. For
there would
be fewer copies needed to achieve a given stack size, but you might
end up with ten times the stack size you really need. Or you might
choose
but this begins to appear fussy. There is no general
algorithm for trading between possible waste of time and possible
waste of space. The choice
is a reasonable compromise which is
hardwired into the code here for simplicity. Note that an alternative
is to add a constant amount to the current stack size, rather
than multiply by a constant amount. This has the advantage of
bounding the possible waste of space. But then the increment can only
sensibly be specified by the user. (The current java.util
implementation of the
Vector
class, offers the option of specifying an additive capacity increment.
Otherwise, a multiplicative increment of 2 is used by default, as
here.)
Note that the java.util package provides a Stack class which extends the Vector class. This also provides a peek method enabling an application to look at the object at the top of the stack without removing it from the stack, and a search method for locating an object in the stack. These would be very easy to implement. The implementation of Stack as an extension of the Vector class is at odds with the present approach, since there is no need to implement a stack in this way. It might be implemented by a linked list, for example, as we shall see below.