8. ArrayStack
Having seen what a stack can do
It is time to start discussing how
There are a few implementation issues to consider
How is the data going to be stored?
How will the top of the stack be managed?
8.1. Implementing a Stack with an Array
Arrays are great for storing contiguous data
If an array is used for storing the data in the stack, how should the top of the stack be kept track of?
One way would be to have the top of the stack always be index
0
Pro — Always know where the top is
Con — Every
push
andpop
requires all elements within the stack to be movedIf there are \(n\) elements in the stack, all must be moved
Another option is to have the top be the other end
Pro — No need to move anything for a
push
orpop
If there are \(n\) elements in the stack, none need to move
Con — Must keep track of which index the top currently is
Although both strategies work, the latter will be used since the
push
andpop
operations require less work
8.1.1. Implementation Issues
An array will be used to hold the data
The
top
will always refer to the next available spot in the arrayDue to zero based indexing, the value of
top
will also correspond to the number of elements in the stackAll
push
operations will happen at thetop
index and require an update totop
All
pop
operations will happen at thetop - 1
index and require an update totop
8.2. Implementation
The
ArrayStack
class will implement theStack
interfaceThis ensures that the
ArrayStack
implementation actually implements the operations required to make it aStack
The
ArrayStack
is aStack
Anything expecting a
Stack
will be happy getting anArrayStack
since it is a stack
Note in the below example where it specifies
ArrayStack<T> implements Stack<T>
The fields are
An integer
top
to keep track of where the top of the stack isThe
stack
array to hold the elements in the stackSince the
ArrayStack
is generic its type isT
6/**
7 * Implementation of a stack with an array as the container. The array container will automatically "grow" to
8 * accommodate adding beyond the initial capacity.
9 *
10 * @param <T> Type of elements that are to be in the stack
11 */
12public class ArrayStack<T> implements Stack<T> {
13
14 private static final int DEFAULT_CAPACITY = 100;
15 private T[] stack;
16 private int top;
Warning
When starting to implement an interface, the IDE may produce a error saying that the interface is not implemented. This is because Java is expecting all abstract methods from the interface to be implemented. This error will go away once all abstract methods are implemented.
8.2.1. Constructors
Like the
ContactList
example, there will be two constructorsOne making use of the default value
The other to create an array of a specified starting capacity
21 /**
22 * Create an empty ArrayStack of the default capacity.
23 */
24 public ArrayStack() {
25 this(DEFAULT_CAPACITY);
26 }
27
28 /**
29 * Create an empty ArrayStack with the specified capacity.
30 *
31 * @param initialCapacity Starting capacity of the fixed length array
32 */
33 @SuppressWarnings("unchecked")
34 public ArrayStack(int initialCapacity) {
35 top = 0;
36 // Generic types cannot be instantiated, so an array of type "Object" is created that is then cast to type T.
37 // This does generate a compile time warning that is being suppressed with the @ annotation.
38 stack = (T[]) new Object[initialCapacity];
39 }
This is making use of constructor chaining
Notice that the array being created is an array of type
Object
that iscast
to the generic typeT
Java forbids creating a generic array
Details are outside the scope of this topic and likely class
When doing this, Java will warn that there is now an unchecked type conversion
Java can’t guarantee that the cast will work right
This can be ignored, however, the warning may be suppressed by adding the following before the constructor
@SuppressWarnings("unchecked")
Creating an instance
Stack<Integer> s = new ArrayStack<>(5);
8.2.2. Push
44 @Override
45 public boolean push(T element) {
46 if (size() == stack.length) {
47 stack = expandCapacity(stack);
48 }
49 stack[top] = element;
50 top++;
51 return true;
52 }
53
54 /**
55 * Returns a new array with double the size of the stack array container and copy the contents from the old array to
56 * the new array.
57 */
58 @SuppressWarnings("unchecked")
59 private T[] expandCapacity(T[] oldStack) {
60 T[] newStack = (T[]) new Object[oldStack.length * 2];
61 for (int i = 0; i < oldStack.length; i++) {
62 newStack[i] = oldStack[i];
63 }
64 return newStack;
65 }
Notice the
@Override
annotation before thepush
methodThis tells the compiler that the method
push
from the interface is being overriddenIt is not required to include this annotation, but it can help eliminate errors
Like the
ContactList
example, a privateexpandCapacity
method is includedIf trying to
push
to a stack that has a fullstack
array,expandCapacity
is calledThe private
expandCapacity
method willMake a new and larger array
Copy the contents of the old
stack
array to the new arrayReturn the new array
8.2.3. Pop
and Peek
The
pop
andpeek
methods will be similar, except peek leaves the stack unchanged
69 @Override
70 public T pop() {
71 if (isEmpty()) {
72 throw new NoSuchElementException("Empty stack");
73 }
74 T returnElement = stack[top - 1];
75 stack[top - 1] = null;
76 top--;
77 return returnElement;
78 }
79
80 @Override
81 public T peek() {
82 if (isEmpty()) {
83 throw new NoSuchElementException("Empty stack");
84 }
85 return stack[top - 1];
86 }
8.2.3.1. Exceptional Situations
What should happen if someone tries to
pop
orpeek
from an empty stack?Ignore it and do nothing?
Crash the program?
Something else?
What should be done in this situation is not up to those implementing the stack
As a rule, one should follow the principal of least surprise
Consider someone using the
ArrayStack
class — should they ever expect to get nothing back when requesting the top?No — the
pop
andpeek
methods explicitly say they return a value
Perhaps it’s more reasonable to assume that the request was invalid in the first place
An exceptional thing happened
Remember, the
ArrayStack
is designed to be general purpose and can be used in many situationsWhat should be done when calling
pop
orpeek
on an empty stack will depend on the specific situationThe point is, when implementing the
ArrayStack
, it is not possible to know what should be done in the exceptional situationWhat can be done, however, is to throw an exception to inform the user that something exceptional happened
Then it is up to the user to deal with the exceptional situation as they see fit
8.2.4. size
and isEmpty
90 @Override
91 public int size() {
92 return top;
93 }
94
95 @Override
96 public boolean isEmpty() {
97 return size() == 0;
98 }
Notice how, because of zero based indexing,
top
is bothThe index of the next available spot in the
stack
arrayThe number of elements in the stack
8.2.5. toString
102 @Override
103 public String toString() {
104 StringBuilder builder = new StringBuilder();
105 for (int i = 0; i < size(); i++) {
106 builder.insert(0, ", ");
107 builder.insert(0, stack[i]);
108 }
109 return builder.toString();
110 }
Ideally the top element in the stack would be the left most element in the string representation of a stack
However, index
0
in thestack
array is the bottom of the stackFor this reason, each element is inserted to the front of the string builder
8.2.6. equals
and hashCode
114 @Override
115 public final boolean equals(Object o) {
116 if (this == o) {
117 return true;
118 }
119 if (o == null || getClass() != o.getClass()) {
120 return false;
121 }
122 ArrayStack<?> that = (ArrayStack<?>) o;
123 return Arrays.equals(this.stack, 0, this.size(), that.stack, 0, that.size());
124 }
125
126 @Override
127 public final int hashCode() {
128 int result = Objects.hash(top);
129 for (int i = 0; i < size(); i++) {
130 result = result * 97 + Objects.hashCode(stack[i]);
131 }
132 return result;
133 }
Two
ArrayStack
objects are considered equal if the contents of thestack
arrays are equal
Note
Like the other methods in the class, the @Override
annotation on the toString
, equals
, and hashCode
methods tell the compiler that the method is overriding one in another class. However, unlike push
, pop
,
peek
, size
, and isEmpty
methods, the overridden methods are not from the Stack
interface, but the
Object
class. All classes inherit from the Object
class which has implementations of the toString
,
equals
, and hashCode
methods.
8.3. For Next Time
Finish reading Chapter 3
16 pages
8.3.1. Playing Code
Download and play with
If everything was done correctly, the following code from
PlayingArrayStack
should work
1import java.util.NoSuchElementException;
2
3public class PlayingArrayStack {
4 public static void main(String[] args) {
5 // Create an ArrayStack
6 Stack<Integer> myStack = new ArrayStack<>(5);
7
8 // Check stack is empty
9 System.out.println(myStack.size());
10 System.out.println(myStack.isEmpty());
11 System.out.println(myStack);
12
13 // Test push
14 myStack.push(0);
15 myStack.push(1);
16 myStack.push(2);
17 myStack.push(3);
18 myStack.push(4);
19 System.out.println(myStack.size());
20 System.out.println(myStack.isEmpty());
21 System.out.println(myStack);
22
23 // Test expand capacity
24 myStack.push(10);
25 myStack.push(11);
26 myStack.push(12);
27 myStack.push(13);
28 myStack.push(14);
29 System.out.println(myStack.size());
30 System.out.println(myStack.isEmpty());
31 System.out.println(myStack);
32
33 // Test peek
34 System.out.println(myStack.peek());
35 System.out.println(myStack.size());
36 System.out.println(myStack.isEmpty());
37 System.out.println(myStack);
38
39 // Test Pop
40 System.out.println(myStack.pop());
41 System.out.println(myStack.pop());
42 System.out.println(myStack.pop());
43 System.out.println(myStack.pop());
44 System.out.println(myStack.pop());
45 System.out.println(myStack.pop());
46 System.out.println(myStack.pop());
47 System.out.println(myStack.pop());
48 System.out.println(myStack.pop());
49 System.out.println(myStack.pop());
50 System.out.println(myStack.size());
51 System.out.println(myStack.isEmpty());
52 System.out.println(myStack);
53
54 // Test peek and pop throwing exception
55 try {
56 myStack.peek();
57 } catch (NoSuchElementException e) {
58 e.printStackTrace();
59 }
60 try {
61 myStack.pop();
62 } catch (NoSuchElementException e) {
63 e.printStackTrace();
64 }
65 }
66}