4

I've been wondering for a while why an ArrayList does not have a stack-like interface (think push() and pop())

I frequently find myself using these constructs in Java:

  • list.get(list.size() - 1);
  • list.remove(list.size() -1);

While I rather do something like:

  • list.peek();
  • list.pop();

I know the legacy class Stack exists, which clashes with a potential interface called Stack, but that wouldn't stop them from making a stack-like interface with a different name.

A Deque also exists, but it can not be (efficiently) backed by an array-like collection for obvious reasons. One downside is that it's implementations are often linked-list-like and therefore don't support random access. There are situations where random-access in a stack is desired, like in Reverse Polish notation calculations or in emulators (implementing an actual call stack)

I also know that the ArrayDeque class exists, which is almost a copy of ArrayList (why?) and explicitly warns about inefficient deque-operations that operate on the first element or elements in the middle. It's also yet another class to learn, possibly with it's own quirks that differ from ArrayList. Why was this route chosen instead of adding the Deque interface to ArrayList? (Is it to prevent inexperienced programmers from making unobvious (performance-related) mistakes?)

Basilevs
  • 3,896

4 Answers4

5

We can only guess why the JCF looks today like it does, but one lesson learned in design is what Antoine de Saint-Exupery once said:

A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.

In my experience, this applies to software design as well. A component like an ArrayList does not become necessarily "better" by supporting as many different data type interfaces as possible (even if it could). The smaller the interface of a component is, the less do we we have to learn and remember how it looks like, and the less functions are there which we confuse for each other. (Of course, an interface should not be smaller than it needs to be to form a coherent abstraction.)

How "small" an interface of a container should be ideally is definitely a question of the designers experience, taste and what they have learned at school or university. The usual CS books about stacks, lists, vectors, queues and arrays treat them as distinct abstract data types, so following that tradition and implementing them as different classes seemed to be quite natural.

Hence, for some reason we do not know for sure, the JCF designers seemed to have thought peek and pop are negligible for ArrayList. But when you think peek and pop would be a great addition to your ArrayLists, feel free to implement your own component ExtArrayList which provides the whole ArrayList together with peek and pop.

Doc Brown
  • 218,378
5

Have you seen this?

enter image description here

Stack Overflow - Rule of thumb for choosing an implementation of a Java Collection?

So tell me, how did you land on ArrayList if you wanted to treat it like a LIFO or a queue?

I know ArrayList is popular. If you got stuck with it somehow and don't really have a choice of implementation don't feel bad about wrapping it up in something that lets its client treat it like a stack. It might not be the most performant choice but good enough is good enough.

But don't act like every possible interface that could have been backed by ArrayList should be native to ArrayList. That list of interfaces has no end. They focused on what they made it for and hoped the kitchen sink could be added later.

Also how would anyone know which end list.pop() would pop from? What makes stack so special? Sorry but if I can see the ArrayList behind the stack then your abstraction is leaking.

candied_orange
  • 119,268
1

API is easy to extend, but is very hard to shrink (shrinking breaks backward compatibility and requires a labor intensive deprecation protocol). Therefore adding any methods (and implementing new interfaces) to an API requires careful consideration and should be done as late as reasonable.

As Java is praised for backward compatibility, it can't easily afford to add new APIs. And when it is actually considered, redundant methods like push(o) instead of addLast(o) have lowest priority. Implementing something that was not possible to do before is more important than a convenience function.

Basilevs
  • 3,896
-2

Why doesn't Java implement things that way? One likely reason given early Java's almost-pedantic approach to object-oriented implementations is that lists and stacks are distinct and different data types, with different usages.

A list is a

collection of items that are finite in number and in a particular order. An instance of a list is a computer representation of the mathematical concept of a tuple or finite sequence.

Whereas a stack

is an abstract data type that serves as a collection of elements with two main operations:

  • Push, which adds an element to the collection, and
  • Pop, which removes the most recently added element.

Lists and stacks are different tools that are used to do different things. An "ordered collection" is not "a collection of elements with two operations". Lists are state-based, effectively static sets, and stacks are event-driven dynamic "holding tanks" that return the last object inserted.

One's a screwdriver, one's a hammer. Sure, you can grab a hammer and use it to drive in a wood screw. It might even work. But it's not an efficient use of the tool, and abuses the screw to the point it might fail. And good luck using a screwdriver to drive in a nail.

Likewise with lists and stacks. You can force a list to be a stack by adding to the end as a "push" operation, and removing the last element as a "pop" operation, but it's very likely to be a lot less efficient than a purpose-build stack implementation. And performance can be a critical requirement in the use of any tool. Whereas stacks have no concept of order outside of "this is the last one in", so they can't be forced into any type of "a particular order".