Search code examples
javaeffective-java

Why did Joshua Bloch use 2*size + 1 for resizing the stack in Effective Java?


This is the code I am talking about:

public class Stack {
  private Object[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
  }
  public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
  }
  public Object pop() {
    if (size == 0)
      throw new EmptyStackException();
    return elements[--size];
  }
  /**
   * Ensure space for at least one more element, roughly
   * doubling the capacity each time the array needs to grow.
   */
  private void ensureCapacity() {
    if (elements.length == size)
      elements = Arrays.copyOf(elements, 2 * size + 1);
  }
}

Why not simply keep the last line as elements = Arrays.copyOf(elements, 2 * size);?

The only case where it might have been valid would be if the initial size of Stack was 0. But in this case it is a constant - DEFAULT_INITIAL_CAPACITY (a non zero value). And there is no other overloaded constructor which could take this value from user (or default it to 0)


Solution

  • I interpret it as peace-of-mind defense against a hypothetical future bug. It's true that as written, this class won't have an array capacity of 0, so adding 1 is not strictly necessary, but that assumption could quietly fail once more features are added.

    Examples of possible additional features include those from java.util.ArrayList, which has a trimToSize() method that can set the capacity to 0, and a constructor which allows initializing the data from a (possibly empty) collection, and a constructor that allows explicitly setting the capacity to 0. You can also imagine a feature that reduces this class's allocated capacity automatically when it is emptied. Or maybe someone will edit the DEFAULT_INITIAL_CAPACITY constant. Now imagine that capacity-changing methods become separated by screenfuls of javadoc comments, and split across subclasses. It's easy to forget you were supposed to prevent the capacity becoming 0.

    If ensureCapacity() depends on the size being non-zero, you always have to keep that assumption in mind while you rework the class. Adding +1 is a low-cost change that removes that worry. It's also an example of a simple arithmetic way of dealing with an edge case.