Bradley N. Miller, David L. Ranum, Roman Yasinovskyy, J. David Eisenberg
Section3.5Implementing a Stack in Java
Now that we have clearly defined the stack as an abstract data type, we will turn our attention to using Java to implement the stack. Recall that when we give an abstract data type a physical implementation, we refer to the implementation as a data structure.
As we described in Section 1.15, in Java, as in any object-oriented programming language, the implementation of choice for an abstract data type such as a stack is the creation of a new class. The stack operations are implemented as methods. Further, to implement a stack, which is a collection of elements, it makes sense to utilize the power and simplicity of the collections provided by Java. We will use an ArrayList.
Recall that the ArrayList class in Java provides an ordered collection mechanism and a set of methods. For example, if we have the list [2, 5, 3, 6, 7, 4], we need only to decide which end of the list will be considered the top of the stack and which will be the bottom (also referred to as the base of the stack). Once that decision is made, the operations can be implemented using the list methods such as add and remove.
The following stack implementation (Listing 3.5.1) assumes that the end of the list will hold the top element of the stack. As the stack grows (as push operations occur), new items will be added on the end of the list. pop operations will manipulate that same end.
importjava.util.ArrayList;importjava.util.NoSuchElementException;classStack<T>{/*
* The top of the stack is at the end
* of the ArrayList.
*/ArrayList<T> items;/*
* Create a new stack
*/publicStack(){this.items =newArrayList<T>();}/*
* Returns true if there are no items on the stack;
* false otherwise.
*/publicbooleanisEmpty(){return(this.items.isEmpty());}/*
* Pushes given item on the top of the stack
*/publicvoidpush(T item){this.items.add(item);}/*
* Removes the item on top of the stack and returns it.
* If the stack is empty, throws an exception.
*/publicTpop(){if(this.isEmpty()){thrownewNoSuchElementException("Stack is empty.");}returnthis.items.remove(items.size()-1);}/*
* Returns the item on top of the stack without removing it.
* If the stack is empty, throws an exception.
*/publicTpeek(){if(this.isEmpty()){thrownewNoSuchElementException("Stack is empty.");}returnthis.items.get(items.size()-1);}/*
* Returns the number of items on the stack.
*/publicintsize(){returnthis.items.size();}/*
* Convert to string as an array from bottom to top
*/publicStringtoString(){if(!this.items.isEmpty()){String arrString =this.items.toString();// remove open and closing bracketreturn"bottom ->"+ arrString +"<- top";}else{return"<<empty stack>>";}}}
Remember in our discussion of ArrayList in Subsection 1.9.1 that we put the data type in angle brackets? This allowed us to have an ArrayList<String>, ArrayList<Integer>, etc. We would also like to be able to create a Stack whose elements are of any data type. To do this, we specify in line 4 that a stack has a generic data type. The T is like a variable that, instead of holding an integer, double, or string value, holds a data type. Thus, when we create a Stack<String>, the data type String provides the value that fills in the generic T. The body of the class uses T anywhere that it needs the type of stack we are dealing with.
Now we must write a program that creates a Stack object and then uses it. Listing 3.5.2 shows the Stack class in action as we perform the sequence of operations from Section 3.4. We don’t need to import the Stack class as long as its source file is in the same directory as our test program. (The interactive code in Listing 3.5.2 has been set up properly for this to work.)
It is important to note that we could have chosen to implement the stack using an ArrayList where the top is at the beginning instead of at the end. In this case, the previous pop and append methods would no longer work and we would have to index position 0 (the first item in the list) explicitly using remove and add. The implementation is shown in Listing 3.5.3.
importjava.util.ArrayList;importjava.util.NoSuchElementException;publicclassStack<T>{/*
* The top of the stack is at the beginning
* of the ArrayList.
*/ArrayList<T> items;/*
* Create a new stack
*/publicStack(){this.items =newArrayList<T>();}/*
* Returns true if there are no items on the stack;
* false otherwise.
*/publicbooleanisEmpty(){return(this.items.isEmpty());}/*
* Pushes given item on the top of the stack
*/publicvoidpush(T item){this.items.add(0, item);}/*
* Removes the item on top of the stack and returns it.
* If the stack is empty, throws an exception.
*/publicTpop(){if(this.isEmpty()){thrownewNoSuchElementException("Stack is empty.");}returnthis.items.remove(0);}/*
* Returns the item on top of the stack without removing it.
* If the stack is empty, throws an exception.
*/publicTpeek(){if(this.isEmpty()){thrownewNoSuchElementException("Stack is empty.");}returnthis.items.get(0);}/*
* Returns the number of items on the stack.
*/publicintsize(){returnthis.items.size();}/*
* Convert to string as an array from top to bottom
*/publicStringtoString(){if(!this.items.isEmpty()){String arrString =this.items.toString();// remove open and closing bracketreturn"top ->"+ arrString +"<- bottom";}else{return"<<empty stack>>";}}}
This ability to change the physical implementation of an abstract data type while maintaining the logical characteristics is an example of abstraction at work. However, even though the stack will work either way, if we consider the performance of the two implementations, there is definitely a difference. Recall that the add() and remove() operations for the end of the ArrayList were both . This means that the first implementation will perform push and pop in constant time no matter how many items are on the stack. The performance of the second implementation suffers in that the add(0) and remove(0) operations will both require for a stack of size n. Clearly, even though the implementations are logically equivalent, they would have very different timings when performing benchmark testing.