The Stack ADT/Collection Class

The Concept
Applications
Array-based Implementation/Data Representation
Referenced-based Implementation/Data Representation

The Concept

You're waiting in line to eat at the cafeteria. As you draw nearer to the service counter, you pass by a stack of trays. Following the lead of those ahead of you in line, you grab the tray on top.

After obtaining your meal, you happen to sit at a table not far from the waiting line. You notice that a cafeteria employee —who has emerged from the kitchen walking alongside a battery-powered cart of freshly-washed trays— is about to replenish the stack of trays, which by now has become quite low. The employee, who is old and frail, can lift only one tray at a time. She places the freshly-washed trays onto the top of the stack, one by one.

From these experiences, you make the following observations: When a tray is removed from a stack, it is the one that was on top. When a tray is placed onto a stack, it is placed on top. After considering the implications of this, you arrive at the following conclusion: Among all the trays on a stack at a given moment, the one among them that will be removed first is the one that was placed onto the stack last. (This is not quite as trivial as it may sound, because any number of insertions/removals may occur in the meantime.) That is, a stack of trays obeys a LIFO ("last in, first out") arrival/departure pattern.

The concept of a stack in computer science is analogous to a stack of trays in a cafeteria. A stack is simply a collection of items such that arrivals and departures conform to a LIFO pattern. Another way to look at it is this: A stack is a sequence of items such that all insertions and deletions occur at the same end. Keeping the cafeteria trays in mind, we call this end the "top" of the stack.

Commonly, it is also taken as part of the definition that, among the items currently on a stack, the only one that can be observed is the one at the top. (Imagine that each cafeteria tray has a serial number etched into it, such that it is visible only if no other tray is sitting on top of it.)

For historical reasons, the add/insert operation on stacks is usually called push and the remove/delete operation is usually called pop. Modeling this stack concept as a generic Java interface (where the generic type parameter indicates the data type of the items to be inserted into the stack), we get the following:


/** An instance of an implementing class represents a stack capable of holding 
**  items of the specified type T (the generic type parameter).  A stack is
**  a container having a LIFO ("last-in-first-out") insertion/removal pattern.
**
**  Author: R. McCloskey
**  Date: January 2020
*/
public interface Stack<T> {

   // observers
   // ---------

   /** Returns the number of items on this stack.
   */
   int sizeOf(); 

   /** Returns true iff there are no items on this stack.
   */
   boolean isEmpty(); 

   /** Returns true if this stack fails to have the capacity to store
   **  any more items (i.e., push() would fail), false otherwise.
   */
   boolean isFull(); 

   /** Returns (a reference to) the item at the top of this stack.
   **  pre: !this.isEmpty()
   */
   T topOf();


   // mutators
   // --------

   /* Removes all the items from this stack, leaving it empty.
   ** post: isEmpty()
   */
   void clear();

   /** Places the specified item onto the top of this stack.
   **  pre:  !isFull()
   **  post: Letting elem(k) refer to the k-th item on a stack, counting
   **    from the bottom starting at zero, and letting s refer to this
   **    stack before push() is applied:
   **      this.sizeOf() = s.sizeOf() + 1 &&
   **      this.elem(s.sizeOf()) = item &&
   **      for all k satisfying 0 <= k < s.sizeOf(), this.elem(k) = s.elem(k) 
   */
   void push(T item);

   /** Removes the item at the top of the stack, returning (a reference to) it.
   **  pre: !isEmpty()
   **  post: Letting elem(k) refer to the k-th item on a stack, counting 
   **    from the bottom starting at zero, and letting s refer to this 
   **    stack before push() is applied:
   **      this.sizeOf() = s.sizeOf() - 1 &&
   **      for all k satisfying 0 <= k < sizeOf(), this.elem(k) = s.elem(k) 
   */
   T pop();

}

Notice that the pop() method serves not only as a mutator but also as an observer. Usually, we adhere to the rule that a method should play only one of these roles and not both. The reason for deviating from that here is to provide some convenience to the client program. Specifically, a client wanting to both retrieve and remove the item at the top of a stack (which is quite typical) could do so using an assignment statement such x = stk.pop(). Had pop()'s return type been declared to be void, the code necessary to do the same thing would have been x = stk.topOf(); stk.pop().


Applications of Stacks:

You may find it surprising to learn that, despite the simplicity of the concept, stacks are quite useful in practice.

Managing Program Execution

A stack is used for managing the flow of execution every time you run a program. You are aware of the fact that, when a method is called, the caller's execution is suspended while the method executes. When the called method terminates, execution resumes within the caller at the command immediately following the one making the call. (The address in memory at which this command is stored is called the return address.) This sounds fairly simple, but what happens when a method, having been called, calls another one (or even itself), which calls another one, which calls yet another, etc., etc.? Some systematic method for keeping track of return addresses is needed in order to ensure that, as each method terminates, execution resumes within its caller at the appropriate place. It turns out that the information necessary for keeping track of all this can be managed in quite an orderly manner by using a stack to store the return addresses.

As an example, suppose that we have a program

Method A:         Method B:           Method C:          Method D:
--------          --------            --------           --------
    ...               Call C;            ...                ...
    ...            B1:...                ...                ...
    Call B;           ...                 Call D;           End;
 A1:...               Call C;          C1:...
    ...            B2:End;                End;
    Call C;
 A2:...
    End;

The labels A1, A2, B1, and C1 indicate the addresses of the commands at which execution should resume after the method called on the preceding line finishes. Each time a call occurs, the corresponding return address is pushed onto the run-time stack. Each time a method terminates execution, the return address at the top of the run-time stack is popped and execution resumes at the location that it indicates. During execution of the example program above, the run-time stack will take on the following configurations (with elements written from bottom to top):

   contents        last change
-------------------------------------------------
 1. A1              A calls B; return address is pushed
 2. A1 B1           B calls C; return address is pushed
 3. A1 B1 C1        C calls D; return address is pushed
 4. A1 B1           D terminates; C1 is popped; execution resumes there
 5. A1              C terminates; B1 is popped; execution resumes there
 6. A1 B2           B calls C (again); return address is pushed
 7. A1 B2 C1        C calls D; return address is pushed
 8. A1 B2           D terminates; C1 is popped; execution resumes there
 9. A1              C terminates; B2 is popped; execution resumes there
10. [empty]         B terminates; A1 is popped; execution resumes there
11. A2              A calls C; return address is pushed
12. A2 C1           C calls D; return address is pushed
13. A2              D terminates; C1 is popped; execution resumes there
14. [empty]         C terminates; A2 is popped; execution resumes there

Evaluating Arithmetic Expressions

Another application of stacks is in the evaluation of arithmetic expressions. In order to keep things simple, here we will focus upon so-called fully-parenthesized (infix) arithmetic expressions, abbreviated "FPAE". In such an expression, only infix operators that apply to numbers can appear (which excludes unary + and -). By "fully-parenthesized", we mean that the expression contains a mated pair of parentheses for every occurrence of an operator symbol. That is, the left operand of each operator is immediately preceded by  (  and the right operand is immediately followed by  ) . An example of such an expression, annotated to show the connections between parentheses and operators, is

(((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2))))
|||___|__| |  | | |  | ||___|__| | |__|__||||
||_________|__| | |  | |_________|________|||
|               | |__|_____________________||
|_______________|___________________________|

We can define FPAE's (recursively) as follows:

An FPAE is either

  1. a numeric literal, or
  2. an expression of the form  ( F op G ) 
where F and G are themselves FPAE's and op is an infix arithmetic operator (e.g., +, -, etc.). We refer to FPAE's of the first form as atomic and those of the second form as compound.

Using a context-free grammar, we can state the definition like this:

<FPAE> ⟶   <numeric literal>
<FPAE> ⟶   ( <FPAE> <operator> <FPAE> )
<operator> ⟶    +   |   −   |   *   |   /

Note: The boldfaced vertical bars are used for separating alternatives. Hence, the third line of the grammar says that an operator is either a plus sign, or a minus sign, or etc., etc. End of note.

How does one evaluate such an expression? Most likely, you would find an immediately evaluable compound subexpression (i.e., one of the form (A op B), where A and B are numeric literals), you would evaluate it, and then you would replace it by the corresponding numeric literal. You would repeat this until the original expression had been reduced to a single numeric literal constituting the final result.

For the sake of making this precise, observe that, in an FPAE, the leftmost immediately evaluable subexpression is always the one ending with the leftmost right parenthesis. Suppose that we always choose that subexpression as the one to evaluate next. Applying this strategy to the FPAE given above, we get, on successive iterations:

   (((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2))))

=  ((   14    + 2) + (3 * ((10 - 4) / (1 + 2))))

=  (         16    + (3 * ((10 - 4) / (1 + 2))))

=  (         16    + (3 * (   6    / (1 + 2))))

=  (         16    + (3 * (   6    /    3   )))

=  (         16    + (3 *          2         ))

=  (         16    +    6                     )

=                 22

In each expression, the leftmost immediately evaluable subexpression is underlined.

Using our human common sense, this was easy. But how can we give precise instructions to a computer —which has absolutely no sense— that would make it correctly evaluate such an expression? Letting E denote the FPAE to be evaluated, our approach may be expressed in pseudocode as follows:

place a cursor at the beginning of E;
while (E is not a numeric literal) {
   advance the cursor until encountering a right parenthesis;
   evaluate the (immediately evaluable) subexpression ending at that right parenthesis 
   replace that subexpression with the corresponding numeric literal;
}

Although this is still somewhat vague, it provides a basis for a more precise algorithm. Among the details to be worked out is how the program determines, upon encountering a right parenthesis, which subexpression is to be evaluated. One way would be to scan to the left (i.e., backwards) until encountering the matching left parenthesis. Necessarily, between the two parentheses there would be a numeric literal, an operator, and another numeric literal. (Either or both of those numeric literals could be the result of having evaluated a complicated subexpression earlier.) How does the program scan to the left? In what kind of storage structure do the values of already-evaluated subexpressions reside?

One way to store them is by using two stacks, which we refer to as the operand stack and operator stack, respectively. As we scan the expression from left to right, on the latter we store the operator symbols and on the former we store the values of the operands. Upon encountering a right parenthesis (which, as noted before, indicates that we have reached the end of the leftmost immediately evaluable compound subexpression), we pop an operator symbol off one stack and two values from the other stack, we apply that operator to those two values, and then we push the result back onto the operand stack. After the last token of the FPAE has been processed, its value will be the lone value remaining on the operand stack.

In order to prove that this works, we can show that each time a right parenthesis is encountered, the operator to which it corresponds is at the top of the operator stack and the (values of the) two operands to which it corresponds are the top two numbers on the operand stack. Such a proof is omitted, but the reader is encouraged to do several examples, after which he should be convinced of the claim's plausibility.

We formalize the above with the following Java-like method. It assumes the existence of classes Expr, Token, and Operator having the instance methods that are invoked and a class StackX that implements the interface Stack shown above. For simplicity, it also assumes that all numeric literals describe integers.

/* pre:  e is a syntactically correct FPAE
** post: value returned is that obtained by evaluating e
*/
public Integer evaluate( Expr e ) {

   Stack<Integer> operandStk = new StackX<Integer>();
   Stack<Operator> operatorStk = new StackX<Operator>();
   Token t = e.firstToken();

   while (e.hasMoreTokens()) {
      t = e.nextToken();
      if (t is a left parenthesis)       { }
      else if (t is an integer literal)  { operandStk.push(t); }
      else if (t is an operator)         { operatorStk.push(t); }
      else if (t is a right parenthesis) {
         Integer y = operandStk.pop();
         Integer x = operandStk.pop();
         Operator op = operatorStk.pop();
         operandStk.push( op.apply(x,y) ); // apply op to x and y; push result
      }
   }
   return operandStk.topOf();
}

As indicated by its precondition, the method above assumes that its parameter is a syntactically correct FPAE. Interestingly, it will work "correctly" even if there are certain syntactic "irregularities". For example, left parentheses are optional! (Which tells us that left parentheses are not really needed and serve only as visual cues to the reader.) As another example, in a subexpression whose left (respectively, right) operand is a numeric literal, no harm will result from the operator appearing before (resp., after) that literal. Examples: 5 + 3) - 2)( + 5 (3 - 2));   ((5 + 3) 2 -)).

Note: If we wanted the method to detect syntax errors in the given expression, an appropriate approach would be to use only one stack rather than two. In scanning the expression from left-to-right, all tokens would be pushed onto the stack, except for )'s. Upon encountering one of these, we would pop the stack four times, the expectation being that the four items popped would be, in order,

  1. the right operand of the subexpression ended by that )
  2. the operator of that subexpression
  3. the left operand of that subexpression
  4. the right parenthesis's mate
If any of the popped items were not of the correct type (e.g., a left parenthesis rather than a number), or if the stack became empty before the fourth pop, that would signal a syntax error in the original FPAE. End of note.


Example:

The following table traces the execution of the algorithm described above when applied to the fully-parenthesized expression shown above. The left column of each row shows the changes that occur to the pair of stacks (operand stack on left, operator stack to its right) as a result of processing a right parenthesis. The right column of each row shows the original FPAE, altered to show the position of the cursor using a boldfaced right square bracket in place of the right parenthesis that is actually there. Underneath that is, in effect, the partially-evaluated expression as it exists immediately after the right parenthesis at the cursor's position has been processed.

The first row, for example, shows that, upon encountering the leftmost right parenthesis, 15 and 1 already have been pushed onto the operand stack and the '−' operator onto the operator stack. The result of processing that right parenthesis is that 14 replaces the 15 and 1 on the operand stack (because 15−1 = 14) and the operator stack, the '−' having been popped, is empty. In general, the operand stack will contain the values "waiting" to have an operator applied to them and the operator stack will contain all those operators that are yet to be applied (because their right operands have not yet been reduced to an integer value).

|    | |   |
|  1 | |   |        |    | |   |
| 15 | | - |  ==>   | 14 | |   |
+----+ +---+        +----+ +---+
(((15 - 1] + 2) + (3 * ((10 - 4) / (1 + 2))))

((   14    + 2) + (3 * ((10 - 4) / (1 + 2))))
|    | |   |
|  2 | |   |        |    | |   |
| 14 | | + |  ==>   | 16 | |   |
+----+ +---+        +----+ +---+
(((15 - 1) + 2] + (3 * ((10 - 4) / (1 + 2))))

(       16      + (3 * ((10 - 4) / (1 + 2))))
|    |
|  4 | |   |
| 10 | | - |        |  6 | |   |
|  3 | | * |        |  3 | | * |
| 16 | | + |  ==>   | 16 | | + |
+----+ +---+        +----+ +---+
(((15 - 1) + 2) + (3 * ((10 - 4] / (1 + 2))))

(       16      + (3 * (    6    / (1 + 2))))
|    |
|  2 | |   |        |    | |   |
|  1 | | + |        |  3 | |   |
|  6 | | / |        |  6 | | / |
|  3 | | * |        |  3 | | * |
| 16 | | + |  ==>   | 16 | | + |
+----+ +---+        +----+ +---+
(((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2])))

(       16      + (3 * (    6    /    3   )))
|    |    
|  3 | |   |        |    |
|  6 | | / |        |  2 | |   |
|  3 | | * |        |  3 | | * |
| 16 | | + |  ==>   | 16 | | + |
+----+ +---+        +----+ +---+
(((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2)]))

(       16      + (3 *          2          ))
|    | |   | 
|  2 | |   |        |    |
|  3 | | * |        |  6 | |   |
| 16 | | + |  ==>   | 16 | | + |
+----+ +---+        +----+ +---+
(((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2))])

(       16      +          6                )
|    | 
|  6 | |   |        |    |
| 16 | | + |  ==>   | 22 | |   |
+----+ +---+        +----+ +---+
(((15 - 1) + 2) + (3 * ((10 - 4) / (1 + 2)))]

                 22


An Array-based Implementation/Data Representation

The most obvious way to represent a stack using an array turns out to be a good way of doing it. We simply store —in elements 0, 1, etc., of the array (call it items[])— the items that are currently on the stack, from bottom to top. In order to perform a pop or push, there must be some way to tell which location of the array corresponds to the top of the stack. To fulfill this purpose, we use an instance variable numItems of type int, whose value indicates the number of items currently occupying the stack. That is, the values occupying array segment items[0..numItems) should correspond to the items on the stack. In particular, the item stored in items[numItems-1] (assuming that numItems > 0) is the item at the top of the stack.

   +-----+
N-1| --- |
 . |  .  |
 . |  .  |
 . |  .  |
 6 | --- |
 5 | --- |
 4 | DOG |
 3 | CAT |
 2 | BUG |       
 1 | EEL |     +-----+
 0 | COW |     |  5  |
   +-----+     +-----+
    items     numItems 

Under this representation scheme, each method in the class can be written using code that is very simple (for a reader to follow) and very efficient (for a computer to execute).

As an example, suppose that we have a stack of creatures from the class Animal. For simplicity, each animal will be denoted by its name (e.g., COW). The picture to the right corresponds to the representation of a stack with five animals on it. The values in array elements 5, 6, ..., N-1 (where N == items.length) are shown as "---", which is intended to indicate that they are irrelevant.

The only serious question that arises in implementing this approach is what to do in response to a push when the array items[] is "full" (i.e., the number of items on the stack is equal to items.length). One reasonable answer is to create a larger array, copy the elements of items[] into that array, and then make items[] refer to the new array. In effect, this lengthens items[] at a cost (in running time) that is proportional to its current length (i.e., its length before lengthening it!).

But by how much should we lengthen the array? By one element? By 10? Actually, it turns out that, in order to ensure that the total cost of all lengthenings is (at worst) proportional to the total number of push operations performed upon the stack during its lifetime, we should lengthen the array each time by some fraction of its current length. A good fraction to use is 1, which would mean that the array is doubled in length each time.

In order to avoid wasting space, the pop() method should detect when the number of items on the stack has become so few, relative to items.length, that items[] should be contracted (i.e., made shorter in length). It turns out that one good strategy is to cut the array in half whenever a pop reduces the number of items on the stack to less than a fourth of the array's length.

The resulting class would look much like the following.

/** An instance of this class represents a stack capable of holding items
**  of the specified type T (the generic type parameter).
**  The implementation is based upon storing the stack items in an array.
**
**  Author: R. McCloskey
**  Date: January 2020
*/

public class StackViaAry<T> implements Stack<T> {

   // symbolic constant
   // -----------------

   private static final int INIT_CAPACITY_DEFAULT  = 8;


   // instance variables
   // ------------------

   protected int numItems;  // # items occupying the stack
   protected T[] items;     // holds (references to) the items on the stack


   // constructors
   // ------------

   /* Establishes this stack to be empty.
   */
   public StackViaAry(int initCapacity) {
      numItems = 0;
      items = (T[])(new Object[initCapacity]);
   }

   /* Establishes this stack to be empty.
   */
   public StackViaAry() { this( INIT_CAPACITY_DEFAULT); }


   // observers
   // ---------

   public boolean isEmpty() { return sizeOf() == 0; }

   public boolean isFull() { return false; }

   public int sizeOf() { return numItems; }

   public T topOf() { return items[numItems-1]; }

   /* Returns a list of the (images of the) items in the stack
   ** going from top to bottom, separated by spaces and enclosed
   ** in square brackets.
   ** Example:  "[ cat in the hat ]"
   */
   public String toString() {
      StringBuilder result = new StringBuilder("[ ");
      for (int i=0; i != numItems; i++) {
         result.append(item(i) + " ");
      }
      return result.append("]").toString();
   }


   /* Returns the k-th item on the stack, counting from the top
   ** starting at zero.
   ** pre: 0 ≤ k < sizeOf()
   */
   protected T item(int k) { return items[numItems - 1 - k]; }


   // mutators
   // --------

   public void clear() {
      // This loop is not necessary, but it could aid the garbage collector.
      for (int i=0; i != numItems; i++) {
         items[i] = null;
      }
      numItems = 0;
   }

   public void push( T item ) {
      if (numItems == items.length) {
         // items[] is full, so double its length by creating a new array
         // (having double the length), copying the values from items[]
         // into the new array, and then making items[] refer to the new array
         T[] temp = (T[])(new Object[2 * items.length]);
         arrayCopy(items, temp, numItems);
         items = temp; 
      } 
      items[numItems] = item;
      numItems = numItems + 1;
   }


   public T pop() {
      T result = items[numItems-1];
      items[numItems-1] = null;  // to aid garbage collection
      numItems = numItems - 1;

      if (items.length >= 2 * INIT_CAPACITY_DEFAULT && 
          items.length >= 4 * numItems)
      {
         // The length of items[] is at least twice the default initial 
         // capacity and at least four times the stack's size, so cut the 
         // length of items[] in half.
         T[] temp = (T[])(new Object[items.length / 2]);
         arrayCopy(items, temp, numItems);
         items = temp;
      }
      return result;
   }


   // private  utility
   // ----------------

   /* Copies values in source[0..length) into dest[0..length)
   ** pre: 0 ≤ length ≤ dest.length, source.length
   */
   private void arrayCopy(T[] source, T[] dest, int length)
   {
      System.arraycopy(source, 0, dest, 0, length);
      // alternative:
      // for (int i=0; i != length; i++)
      //    { dest[i] = source[i]; }
   }
} 

A Reference-Based Implementation/Data Representation

One of the less attractive features of using an array as the basis upon which to represent a stack is that, when the stack's size becomes "incompatible" with the size of the array (i.e., when either the stack has become too large to fit into the array or it has become so small that a significant portion of the array is unused), it becomes prudent to create a new, differently-sized array and to copy all the relevant data into it from the old array. In order to ensure that this "size-change" operation does not dominate the time required to process stack operations, the new array's size should be significantly different from the old one. (In our implementation, the new array is made to be either double or half the size of the old one.)

Hence, although the abstract stack structure is growing and shrinking in small increments, the underlying structure used to represent it is growing and shrinking in large increments.

A concrete representation that makes use of references, rather than an array, can conveniently grow and shrink incrementally, just like the abstract structure that it represents. To achieve this, we make use of a (generic) class, Link1, that provides one-directional linking capabilities. An object of this class can be depicted as

  item   next
+------+------+
|   *  |   *--+----> another Link1<T> object
+---+--+------+
    |
    |
    v
  +----+
  |    | (object of type T)
  +----+

That is, a Link1<T> object contains a reference to an object (of type T) and a reference to another object of type Link1<T>. An implementation of this class is as follows:

/** An instance of this class contains a reference to an object of the
**  specified type T (the generic type parameter) and a reference to
**  an object of the same kind (i.e., Link1).  The idea is that objects
**  of this class can be used as building blocks to form one-directional 
**  linked structures (i.e., one-way lists).
*/
public class Link1<T> { 

   // instance variables 
   // ------------------

   public T item;
   public Link1<T> next;

 
   // constructors
   // ------------

   public Link1(T item, Link1<T> next) { 
      this.item = item; this.next = next;
   }

   public Link1(T item) { this(item, null); }

   public Link1() { this(null, null); }

}

Notice that the instance variables of Link1 are public, which differs from our usual practice of making such variables either private or protected. That's because we are not interested in "hiding" its implementational details; rather, we intend for Link1 objects to be manipulated "directly" by objects of classes that represent linked structures.

Using Link1 as a basis, we can represent the stack containing COW, CAT, DOG, BUG, and ANT objects (going from top to bottom) as follows, where top is an instance variable pointing to the Link1<T> object corresponding to the top item on the stack. A second instance variable, numItems, stores the number of items on the stack. (The numItems variable is not necessary, but its existence makes the sizeOf() method easier to implement and more efficient than would be possible otherwise.)

+-----+---+     +-----+---+     +-----+---+     +-----+---+     +-----+---+
| COW | *-+---->| CAT | *-+---->| DOG | *-+---->| BUG | *-+---->| ANT | *-+--!
+-----+---+     +-----+---+     +-----+---+     +-----+---+     +-----+---+
     ^
     |
     |
   +-+-+      +---+
   | * |      | 5 |
   +---+      +---+
    top      numItems

For simplicity, we have simply written each Link1 object's animal name inside (the box representing) its first field. In reality, each such field is a reference (i.e., pointer) to the corresponding animal object.

Following this approach, here is the stack class that we derive:

/** An instance of this class represents a stack capable of holding items
**  of the specified type T (the generic type parameter).
**  The underlying implementation makes use of a linked structure of
**  objects arising from the Link1 class.
**
**  Author: R. McCloskey
**  Date: January 2020
*/
public class StackViaLink1<T> implements Stack<T> {

   // instance variables
   // ------------------

   protected Link1<T> top;  // reference to object holding top item on stack
   private int numItems;    // # of items on the stack


   // constructors
   // ------------

   /* Establishes this stack as being empty.
   */
   public StackViaLink1() { clear(); }


   // observers
   // ---------

   public int sizeOf() { return numItems; }

   public boolean isEmpty() { return sizeOf() == 0; }

   public boolean isFull() { return false; }

   public T topOf() { return top.item; }

   public String toString() {
      Link1 pntr = top;
      StringBuilder s = new StringBuilder();
      while (pntr != null) {
         s.append(pntr.item + ",");
         pntr = pntr.next;
      }
      return s.substring(0,Math.max(0, s.length()-1));
   }


   // mutators
   // --------

   public void clear() { top = null; numItems = 0; }

   public void push( T item ) { 
      top = new Link1<T>( item, top );
      numItems = numItems + 1;
   }

   public T pop() { 
      T result = top.item;
      top = top.next; 
      numItems = numItems - 1;
      return result;
   }

}