1. 概述

本文将快速介绍 java.util.Stack 类及其核心用法。栈(Stack)是一种通用的数据结构,遵循 LIFO(后进先出)原则,支持在常数时间内压入(push)和弹出(pop)元素。

⚠️ 重要提示:对于新项目,优先使用 Deque 接口及其实现类(如 ArrayDeque)。Deque 提供了更完整和一致的 LIFO 操作集。但在处理遗留代码时,我们仍可能遇到 Stack 类,因此深入理解它很有必要。

2. 创建栈

使用无参构造函数创建空栈实例:

@Test
public void whenStackIsCreated_thenItHasSizeZero() {
    Stack<Integer> intStack = new Stack<>();
    
    assertEquals(0, intStack.size());
}

这会创建一个默认容量为 10 的栈。当元素数量超过容量时,栈会自动扩容(容量翻倍),但移除元素后容量不会收缩。

3. 栈的同步性

Stack 直接继承自 Vector,这意味着它和父类一样是同步实现。但在不需要同步的场景下,建议使用 ArrayDeque 以避免不必要的性能开销。

4. 向栈添加元素

使用 push() 方法将元素压入栈顶,该方法会返回被添加的元素:

@Test
public void whenElementIsPushed_thenStackSizeIsIncreased() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(1);
    
    assertEquals(1, intStack.size());
}

push()addElement() 效果相同,区别在于后者返回操作结果而非被添加元素

也可以批量添加元素:

@Test
public void whenMultipleElementsArePushed_thenStackSizeIsIncreased() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    
    boolean result = intStack.addAll(intList);
    
    assertTrue(result);
    assertEquals(7, intList.size());
}

5. 从栈获取元素

5.1 弹出元素(移除并返回)

使用 pop() 移除并返回栈顶元素:

@Test
public void whenElementIsPoppedFromStack_thenElementIsRemovedAndSizeChanges() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);

    Integer element = intStack.pop();
    
    assertEquals(Integer.valueOf(5), element);
    assertTrue(intStack.isEmpty());
}

5.2 查看栈顶元素(不移除)

使用 peek() 获取栈顶元素但不移除:

@Test
public void whenElementIsPeeked_thenElementIsNotRemovedAndSizeDoesNotChange() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);

    Integer element = intStack.peek();

    assertEquals(Integer.valueOf(5), element);
    assertEquals(1, intStack.search(5));
    assertEquals(1, intStack.size());
}

6. 在栈中搜索元素

6.1 搜索位置

search() 返回元素到栈顶的距离(栈顶位置为 1):

@Test
public void whenElementIsOnStack_thenSearchReturnsItsDistanceFromTheTop() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(8);

    assertEquals(2, intStack.search(5));
}

关键点

  • 若存在多个相同元素,返回最靠近栈顶的元素位置
  • 栈顶元素位置为 1
  • 未找到元素时返回 -1

6.2 获取元素索引

使用 indexOf()lastIndexOf() 获取元素索引:

@Test
public void whenElementIsOnStack_thenIndexOfReturnsItsIndex() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    
    int indexOf = intStack.indexOf(5);
    
    assertEquals(0, indexOf);
}

lastIndexOf() 始终返回最靠近栈顶的元素索引,与 search() 类似但返回索引值(从 0 开始):

@Test
public void whenMultipleElementsAreOnStack_thenIndexOfReturnsLastElementIndex() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(5);
    intStack.push(5);
    
    int lastIndexOf = intStack.lastIndexOf(5);
    
    assertEquals(2, lastIndexOf);
}

7. 从栈移除元素

7.1 移除指定元素

使用 removeElement() 移除首个匹配元素:

@Test
public void whenRemoveElementIsInvoked_thenElementIsRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(5);

    intStack.removeElement(5);
    
    assertEquals(1, intStack.size());
}

使用 removeElementAt() 按索引移除:

@Test
public void whenRemoveElementAtIsInvoked_thenElementIsRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(7);
    
    intStack.removeElementAt(1);
    
    assertEquals(-1, intStack.search(7));
}

7.2 批量移除元素

使用 removeAll() 移除集合中所有匹配元素:

@Test
public void givenElementsOnStack_whenRemoveAllIsInvoked_thenAllElementsFromCollectionAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    intStack.add(500);

    intStack.removeAll(intList);

    assertEquals(1, intStack.size());
    assertEquals(1, intStack.search(500));
}

使用 clear()removeAllElements() 清空栈:

@Test
public void whenRemoveAllElementsIsInvoked_thenAllElementsAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    intStack.push(5);
    intStack.push(7);

    intStack.removeAllElements();

    assertTrue(intStack.isEmpty());
}

7.3 按条件移除元素

使用 removeIf() 结合条件表达式移除元素:

@Test
public void whenRemoveIfIsInvoked_thenAllElementsSatysfyingConditionAreRemoved() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    
    intStack.removeIf(element -> element < 6);
    
    assertEquals(2, intStack.size());
}

8. 遍历栈

支持 IteratorListIterator 遍历:

  • Iterator:单向遍历
  • ListIterator:双向遍历
@Test
public void whenAnotherStackCreatedWhileTraversingStack_thenStacksAreEqual() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
    intStack.addAll(intList);
    
    ListIterator<Integer> it = intStack.listIterator();
    
    Stack<Integer> result = new Stack<>();
    while(it.hasNext()) {
        result.push(it.next());
    }

    assertThat(result, equalTo(intStack));
}

⚠️ 所有 Stack 返回的迭代器都是 fail-fast 的。

9. 使用 Stream API

作为 Collection 的子类,Stack 支持 Java 8 Stream API:

@Test
public void whenStackIsFiltered_allElementsNotSatisfyingFilterConditionAreDiscarded() {
    Stack<Integer> intStack = new Stack<>();
    List<Integer> inputIntList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9, 10);
    intStack.addAll(inputIntList);

    List<Integer> filtered = intStack
      .stream()
      .filter(element -> element <= 3)
      .collect(Collectors.toList());

    assertEquals(3, filtered.size());
}

10. 总结

本文快速介绍了 Java 核心类 Stack 的关键特性和用法。完整 API 可参考 Javadoc,所有示例代码见 GitHub 仓库


原始标题:Quick Guide to Java Stack | Baeldung