Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

The Java Stack class implements the stack data structure. Java 1.6 introduced the Deque interface, which is for implementing a “double-ended queue” that supports element insertion and removal at both ends.

Now, we can use the Deque interface as a LIFO (Last-In-First-Out) stack as well. Moreover, if we have a look at the Javadoc of the Stack class, we’ll see:

A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class.

In this tutorial, we’re going to compare the Java Stack class and the Deque interface. Further, we’ll discuss why we should use Deque over Stack for LIFO stacks.

2. Class vs. Interface

Java’s Stack is a Class:

public class Stack<E> extends Vector<E> { ... }

That is to say, if we want to create our own Stack type, we have to inherit the java.util.Stack class.

Since Java doesn’t support multiple-inheritance, it can sometimes be hard to extend the Stack class if our class is already a subclass of another type:

public class UserActivityStack extends ActivityCollection { ... }

In the example above, the UserActivityStack class is a sub-class of an ActivityCollection class. Therefore, it cannot also extend the java.util.Stack class. To use the Java Stack class, we may need to redesign our data models.

On the other hand, Java’s Deque is an interface:

public interface Deque<E> extends Queue<E> { ... }

We know a class can implement multiple interfaces in Java. Therefore, implementing an interface is more flexible than extending a class for inheritance.

For example, we can easily make our UserActivityStack implement the Deque interface:

public class UserActivityStack extends ActivityCollection implements Deque<UserActivity> { ... }

Therefore, from the object-oriented design point-of-view, the Deque interface brings us more flexibility than the Stack class.

3. synchronized Methods and Performance

We’ve seen that the Stack class is a subclass of java.util.Vector. The Vector class is synchronized. It uses the traditional way to achieve thread-safety: making its methods “synchronized.

As its subclass, the Stack class is synchronized as well.

On the other hand, the Deque interface is not thread-safe.

So, if thread-safety is not a requirement, a Deque can bring us better performance than a Stack.

4. Iteration Orders

Since both Stack and Deque are subtypes of the java.util.Collection interface, they are also Iterable.

However, what’s interesting is that if we push the same elements in the same order into a Stack object and a Deque instance, their iteration orders are different.

Let’s take a closer look at them through examples.

First, let’s push some elements into a Stack object and check what its iteration order is:

@Test
void givenAStack_whenIterate_thenFromBottomToTop() {
    Stack<String> myStack = new Stack<>();
    myStack.push("I am at the bottom.");
    myStack.push("I am in the middle.");
    myStack.push("I am at the top.");

    Iterator<String> it = myStack.iterator();

    assertThat(it).toIterable().containsExactly(
      "I am at the bottom.",
      "I am in the middle.",
      "I am at the top.");
}

If we execute the test method above, it’ll pass. It means, when we iterate over the elements in a Stack object, the order is from stack bottom to stack top.

Next, let’s do the same test on a Deque instance. We’ll take the ArrayDeque class as the Deque implementation in our test.

Also, we’ll use the ArrayDeque as a LIFO stack:

@Test
void givenADeque_whenIterate_thenFromTopToBottom() {
    Deque<String> myStack = new ArrayDeque<>();
    myStack.push("I am at the bottom.");
    myStack.push("I am in the middle.");
    myStack.push("I am at the top.");

    Iterator<String> it = myStack.iterator();

    assertThat(it).toIterable().containsExactly(
      "I am at the top.",
      "I am in the middle.",
      "I am at the bottom.");
}

This test will pass as well if we give it a run.

Therefore, the iteration order of Deque is from top to bottom.

When we’re talking about a LIFO stack data structure, the proper sequence of iterating over elements in the stack should be from top to bottom.

That is, Deque‘s iterator works the way we expect for a stack.

5. Invalid LIFO Stack Operations

A classic LIFO stack data structure supports only push(), pop(), and peek() operations. Both the Stack class and the Deque interface support them. So far, so good.

However, it’s not allowed to access or manipulate elements by indexes in a LIFO stack since it breaks the LIFO rule.

In this section, let’s see if we can call invalid stack operations with Stack and Deque.

5.1. The java.util.Stack Class

Since its parent Vector is an array-based data structure, the Stack class has the ability to access elements by indexes:

@Test
void givenAStack_whenAccessByIndex_thenElementCanBeRead() {
    Stack<String> myStack = new Stack<>();
    myStack.push("I am the 1st element."); //index 0
    myStack.push("I am the 2nd element."); //index 1
    myStack.push("I am the 3rd element."); //index 2
 
    assertThat(myStack.get(0)).isEqualTo("I am the 1st element.");
}

The test will pass if we run it.

In the test, we push three elements into a Stack object. After that, we want to access the element sitting at the bottom of the stack.

Following the LIFO rule, we have to pop all elements above to access the bottom element.

However, here, with the Stack object, we can just access an element by its index.

Moreover, with a Stack object, we can even insert and remove an element by its index. Let’s create a test method to prove it:

@Test
void givenAStack_whenAddOrRemoveByIndex_thenElementCanBeAddedOrRemoved() {
    Stack<String> myStack = new Stack<>();
    myStack.push("I am the 1st element.");
    myStack.push("I am the 3rd element.");

    assertThat(myStack.size()).isEqualTo(2);

    myStack.add(1, "I am the 2nd element.");
    assertThat(myStack.size()).isEqualTo(3);
    assertThat(myStack.get(1)).isEqualTo("I am the 2nd element.");

    myStack.remove(1);
    assertThat(myStack.size()).isEqualTo(2);
}

The test will also pass if we give it a run.

Therefore, using the Stack class, we can manipulate elements in it just like working with an array. This has broken the LIFO contract.

5.2. The java.util.Deque Interface

Deque doesn’t allow us to access, insert, or remove an element by its index. It sounds better than the Stack class.

However, since Deque is a “double-ended queue,” we can insert or remove an element from both ends.

In other words, when we use Deque as a LIFO stack, we can insert/remove an element to/from the bottom of the stack directly.

Let’s build a test method to see how this happens. Again, we’ll continue using the ArrayDeque class in our test:

@Test
void givenADeque_whenAddOrRemoveLastElement_thenTheLastElementCanBeAddedOrRemoved() {
    Deque<String> myStack = new ArrayDeque<>();
    myStack.push("I am the 1st element.");
    myStack.push("I am the 2nd element.");
    myStack.push("I am the 3rd element.");

    assertThat(myStack.size()).isEqualTo(3);

    //insert element to the bottom of the stack
    myStack.addLast("I am the NEW element.");
    assertThat(myStack.size()).isEqualTo(4);
    assertThat(myStack.peek()).isEqualTo("I am the 3rd element.");

    //remove element from the bottom of the stack
    String removedStr = myStack.removeLast();
    assertThat(myStack.size()).isEqualTo(3);
    assertThat(removedStr).isEqualTo("I am the NEW element.");
}

In the test, first, we insert a new element to the bottom of a stack using the addLast() method. If the insertion succeeds, we attempt to remove it with the removeLast() method.

If we execute the test, it passes.

Therefore, Deque doesn’t obey the LIFO contract either.

5.3. Implementing a LifoStack Based on Deque

We can create a simple LifoStack interface to obey the LIFO contract:

public interface LifoStack<E> extends Collection<E> {
    E peek();
    E pop();
    void push(E item);
}

When we create implementations of our LifoStack interface, we can wrap Java standard Deque implementations.

Let’s create an ArrayLifoStack class as an example to understand it quickly:

public class ArrayLifoStack<E> implements LifoStack<E> {
    private final Deque<E> deque = new ArrayDeque<>();

    @Override
    public void push(E item) {
        deque.addFirst(item);
    }

    @Override
    public E pop() {
        return deque.removeFirst();
    }

    @Override
    public E peek() {
        return deque.peekFirst();
    }

    // forward methods in Collection interface
    // to the deque object

    @Override
    public int size() {
        return deque.size();
    }
...
}

As the ArrayLifoStack class shows, it only supports the operations defined in our LifoStack interface and the java.util.Collection interface.

In this way, it won’t break the LIFO rule.

6. Conclusion

Since Java 1.6, both java.util.Stack and java.util.Deque can be used as LIFO stacks. This article addressed the difference between these two types.

We’ve also analyzed why we should use the Deque interface over the Stack class to work with LIFO stacks.

Additionally, as we’ve discussed through examples, both Stack and Deque break the LIFO rule, more or less.

Finally, we’ve shown one way to create a stack interface obeying the LIFO contract.

As always, the complete source code is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!