Heretofore (can you believe that’s a word?), we’ve seen a direct relationship between ADTs and their implementing DSs, but we’ll add a twist to that mix today…
Let’s talk about another data structure that we’ve actually mentioned many times before, but can formalize now…
Question
What is the call stack, and separately, if we had to think about as a data structure / collection, what would be its hallmarks / properties / operations?
Answer
Answering those questions separately:
- The Call Stack: manages local variable memory, scope, and where functions return to whenever called.
- Operations: new function calls place their stack frame on top of the one that called them in a “stacked” fashion!
It turns out that the Call Stack gets its name from another ADT!
Definition
A stack is an ADT used to store elements in a way that the most recently stored items are the first to be retrieved (FILO: First-in, Last-out).
Remark
FILO Intuition: The most recent thing we insert into a stack is the first thing we get back. Useful for certain scenarios wherein we care about the order in which items in a collection are stored.
Trust me, it’s cool! By analogy, think of a pile of dishes:
- You stack dishes one on top of the other
- Once you’ve stacked dish Z on top of all the other dishes, you can only access dish Z (and cannot access any other dish beneath it) until you pop Z off the top again
Stacks are very useful; in fact, there’s a reason why local variables are stored in the stack, because every function’s variables get stacked on the top when they’re called, and popped off again when you leave the function!
Example
List some other examples of why you might use the FILO behavior of a Stack.
- Retracing your steps in a maze so that you can backtrack and explore every path; moves most recently taken are near the top.
- Some aspects of parsing code, such as determining which parentheses match with what other parentheses in some mathematical expression.
- Some forms of state management in computer graphics, such as applying some style like a background color to a bunch of things before removing that style.
Stack Operations
Stacks support a few basic operations:
push(toPush);adds the given element to the top of the stack.peek();returns the element at the top of the stack.pop();removes the element at the top of the stack.
Stack Implementation
Toolkit
Stacks can actually be implemented using either a LinkedList or an ArrayList!
Since we’re mostly familiar with ArrayLists, let’s challenge ourselves by trying to use a LinkedList to represent a Stack:
Question
OK, Andrew, why would we want to make a data structure that’s a linked list… but more restricted than one?
Answer
This goes back to our public vs. private interface discussion: we want to give users of our classes certain guaranteed behaviors that allow for predictable code execution. If an algorithm requires that only the most recently pushed Node can be accessed at any time, we need to make sure our users don’t tamper with that guaranteed behavior.
With this description in mind, let’s try implementing our own IntStack using the IntLinkedList class we made last time!
Question
Since we’ve done all of that work in the LinkedList class anyways, what class-design mechanism could we use to properly implement the Stack behavior?
Answer
How about inheritance! Let’s just use the IntLinkedList class that we designed previously to implement our desired behavior.
// Remark: Since this package is becoming more than just
// lists, we'd probably rename it to something better, but
// we'll keep it as-is for now just for illustrative purposes
package intlist;
// [?] This inheritance "works," but is it a good idea?
public class IntStack extends IntLinkedList {
IntStack () {
super();
}
public void push (int toPush) {
super.prepend(toPush);
}
public void pop () {
super.removeAt(0);
}
public int peek () {
return super.getAt(0);
}
}Testing it, we see:
package intlist;
public class IntStackTests {
public static void main (String[] args) {
IntStack stacky = new IntStack();
stacky.push(1);
stacky.push(2);
stacky.push(3);
stacky.pop();
// [?] What gets printed here?
System.out.println(stacky.peek());
}
}Question
What’s wrong with our inheritance-structured implementation of
Answer
2 main issues:
- Since our IntStack inherits from IntLinkedList, we still have access to the methods that IntStack does not overload, possibly violating our FILO guarantee if we can access / change elements other than the top.
- It doesn’t seem particularly DRY if we’re just borrowing the LinkedList’s implementation in separate method definitions.
To illustrate problem 1: note that the following still compiles and runs:
package intlist;
public class IntStackTests {
public static void main (String[] args) {
IntStack stacky = new IntStack();
// A stack with prepend?! Odd, but kinda OK...
stacky.prepend(1);
stacky.push(2);
stacky.push(3);
// A stack with removeAt?!?! Not OK!
stacky.removeAt(1);
System.out.println(stacky.peek());
}
}Question
Why is it, based on the operations we described for a Stack above, not OK for one to support the
removeAt(index)method?
Answer
This would violate the FILO guarantee that Stacks are meant to provide, since a value that may have been inserted earlier may be removed before one that was inserted later. This could introduce errors when some user of a Stack needs that guarantee to be honored.
Sometimes we desire this flexibility, and is how the Java Stack is implemented (see JavaDocs and section below), but other times, we want to ensure that only those methods that we want exposed are available to the user.
Question
What tool that we’ve learned about can help us restrict a Stack’s operations to only those intended, even if we’ve implemented it using a LinkedList?
Answer
Create an Interface that removes the undesired methods!
To thus limit our IntStacks to only pop, push, and peek, we could define a new interface that IntStack implements:
package intlist;
public interface TrueIntStack {
public void push (int toPush);
public void pop ();
public int peek ();
}
// [!] Not shown: IntStack implementing TrueIntStackOf course, implementing an interface merely specifies the minimal methods that must be implemented, and does not inherently prevent others (including those inherited) from being made available as well.
To restrict IntStacks to these 3 methods, we would change our tests such that:
package intlist;
public class IntStackTests {
public static void main (String[] args) {
// [!] Note new declared "type" specifies the interface,
// which is OK because the constructor on the RHS is an
// object whose class implements it
TrueIntStack stacky = new IntStack();
// [X] Now: this is a compilation error (as we wanted)
stacky.prepend(1);
stacky.push(2);
stacky.push(3);
// [X] This too is a compilation error (as we wanted)
stacky.removeAt(0);
System.out.println(stacky.peek());
}
}A couple of parting remarks from this example:
Remark
A better approach (solving problem 2) for implementing a Stack: implement its methods in the IntLinkedList class, have IntLinkedList implement TrueIntStack, and then simply create objects using the TrueIntStack interface, e.g.,
TrueIntStack s = new IntLinkedList();
Definition
Note: we could have just as easily maintained a stack as an ArrayList, whereby we pushed and popped items from the back of the list. The choice to implement one as an array vs. linked list will depend heavily on the intended application.
Example
Try implementing an IntStack above using our IntArrayList from the previous lectures; it’ll be pretty simple as well!
Queues
Queues aren’t a whole lot different from stacks, except that instead of pushing to and popping from the top, we queue to the back of the queue and dequeue from the front.
Definition
A queue is an ADT that maintains the FIFO (first in, first out) behavior of insertion and retrieval of its elements.
Remark
FIFO Intuition: The most recent thing we insert into a stack is the last thing we get back. Useful for certain scenarios wherein we care about the order in which items in a collection are stored.
There’s a reason that “queues” in supermarkets and theme parks are named so; people enter them at the back, and then are served at the front!
Example
What are some examples of scenarios in which a Queue might be useful?
- Maintaining an ordered sequence of actions that some agent should carry out, like moving “Up”, then “Right”, then “Up” in a grid maze in sequence.
- Maintaining some time-specified order of admittance, like order in which users of a site can begin ordering tickets or an online game’s server / lobby.
Here’s what that might look like as a singly linked list:

Again, though, you should be aware that a queue is an abstract data type, and that some implementations WILL allow you to access the intermediary nodes behind the front.
Remark
Reflect: why would an ArrayList not be as good of an implementation choice for a Queue as a LinkedList is?
Common Queue Functions:
push(toQueue)(sometimes called enqueue) adds an element to the back of the queue.poll()(sometimes generally referred to as pop() or dequeue()) returns and removes the element from the front of the queue.peek()returns a reference to the element at the front of the queue (least recently added)
Example
Implementing a Queue is left as an exercise, since they’re easily maintained with a LinkedList (which, coincidentally, will be one of your upcoming homeworks heh).
Java Collections Framework
Toolkit
The JCF
Stackprovides an implementation for FILO behavior using a Vector (basically, an ArrayList) (though also recommended is the Deque data structure, which we won’t discuss herein).
The Stack interface can be viewed here.
Stack<String> stacky = new Stack<>();
stacky.add("a");
stacky.add("b");
stacky.add("c");
// pop removes and returns top of stack
stacky.pop();
// peek returns top of stack
// [?] What gets printed here?
System.out.println(stacky.peek());Debug
Warning: because all Queue methods are already implemented using LinkedLists, the Java
Queueis simply an interface, not an instantiable class!
As such, we can either initialize a Queue as a Linked List or some other structures we have yet to learn about.
The Queue interface can be viewed here.
Queue being an interface allows us to specify how we want that to be done, as long as certain implementations define the Queue interface’s methods (in the link above) for example:
// Queue (Interface) implemented with LinkedList
Queue<String> q = new LinkedList<String>();
// add pushes an item to the back of the Queue
q.add("a");
q.add("b");
q.add("c");
// poll dequeues and returns item at front
q.poll();
// peek returns item at front
// [?] What gets printed here?
System.out.println(q.peek());