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

../../_images/array.png

An array of size 10 storing references to six elements. The references to the six elements are stored within the array indices 0 – 5.

  • 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 and pop requires all elements within the stack to be moved

      • If 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 or pop

      • 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 and pop 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 array

  • Due to zero based indexing, the value of top will also correspond to the number of elements in the stack

  • All push operations will happen at the top index and require an update to top

  • All pop operations will happen at the top - 1 index and require an update to top

../../_images/arraystack0.png

An example ArrayStack containing four elements. Note the value stored in top refers to the next available spot in the array — where the next pushed element would go. Also notice that the value in top corresponds to the number of elements currently in the stack.

../../_images/arraystack1.png

The state of the ArrayStack after an element was pushed. Note that the value of top was increased by one such that it refers to the next available spot in the array.

../../_images/arraystack2.png

The state of the ArrayStack after an element was popped. Note that the value of top was decreased by one.

../../_images/arraystack3.png

The state of the ArrayStack after another element was popped. Note that, again, the value of top was decreased by one such that it refers to the next available spot in the array.

8.2. Implementation

  • The ArrayStack class will implement the Stack interface

    • This ensures that the ArrayStack implementation actually implements the operations required to make it a Stack

      • The ArrayStack is a Stack

      • Anything expecting a Stack will be happy getting an ArrayStack 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 is

    • The stack array to hold the elements in the stack

      • Since the ArrayStack is generic its type is T

 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.

../../_images/warning_implement.png

Example of the error IntelliJ will produce if not all abstract methods from the Stack interface are implemented.

8.2.1. Constructors

  • Like the ContactList example, there will be two constructors

    • One 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 is cast to the generic type T

  • 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);

../../_images/arraystack_empty.png

A visualization of an ArrayStack created with a starting capacity of 5. The instance s could have been created with Stack<Integer> s = new ArrayStack<>(5);. Although the type Integer is specified in the declaration, there is nothing within the figure to indicate that the elements within the stack would be of type Integer.

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 the push method

    • This tells the compiler that the method push from the interface is being overridden

    • It is not required to include this annotation, but it can help eliminate errors

  • Like the ContactList example, a private expandCapacity method is included

    • If trying to push to a stack that has a full stack array, expandCapacity is called

    • The private expandCapacity method will

      • Make a new and larger array

      • Copy the contents of the old stack array to the new array

      • Return the new array

8.2.3. Pop and Peek

  • The pop and peek 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 or peek 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 and peek 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 situations

  • What should be done when calling pop or peek on an empty stack will depend on the specific situation

  • The point is, when implementing the ArrayStack, it is not possible to know what should be done in the exceptional situation

  • What 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 both

    • The index of the next available spot in the stack array

    • The 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 the stack array is the bottom of the stack

  • For 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 the stack 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

 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}