1. 概述

Stack(栈)遵循后进先出(LIFO)原则,正确打印其值有时会踩坑。别担心,本文将彻底解决这个问题!

我们将探索从最简单到最高效的多种打印方式,分析各方法的适用场景,并解释为什么在现代Java开发中,Deque(双端队列)比Stack更优。

2. 使用 Stack.toString()

只需快速查看栈内容时,toString() 是你的利器。它内置于Stack类(继承自Vector),能直接输出所有元素。

Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
stack.push(30);

实现示例:

public static void givenStack_whenUsingToString_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(10);
stack.push(20);
stack.push(30);
    System.out.println(stack.toString());
}

输出结果:

[10, 20, 30]

适用场景

  • 快速调试时检查栈内容
  • 不关心格式,只需快速浏览元素

⚠️ 注意

  • 输出包含方括号(可能不符合需求)
  • 输出格式像List,无法明确体现栈结构

3. 使用增强for循环

需要自定义输出格式时,循环遍历是简单粗暴的选择:

public static void givenStack_whenUsingForEach_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(10);
    stack.push(20);
    stack.push(30);

    for (Integer value : stack) {
        System.out.print(value + " ");
    }
}

输出结果:

10 20 30

适用场景

  • 需要更简洁或自定义的打印格式
  • 元素顺序要求不严格时

局限

  • 不保证LIFO顺序(实际按插入顺序打印)

4. 使用 forEach() 循环

Java 8的forEach()提供更简洁写法,但有个关键坑:Stack或Deque的forEach()不会按LIFO顺序打印! 它遵循集合的迭代顺序(即插入顺序)。

示例代码:

public static void givenStack_whenUsingDirectForEach_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(10);
    stack.push(20);
    stack.push(30);
    stack.forEach(element -> System.out.println(element));
}

输出结果:

10
20
30

⚠️ 重要提醒

  • 虽然提供完全的格式控制,但破坏了LIFO顺序
  • 若需LIFO顺序,必须先反转栈:
public static void givenStack_whenUsingStreamReverse_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(20);
    stack.push(10);
    stack.push(30);

    Collections.reverse(stack);
    stack.forEach(System.out::println);
}

修正后输出:

30
10
20

5. 使用 Iterator

Iterator提供逐元素遍历的能力:

public static void givenStack_whenUsingIterator_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(10);
    stack.push(20);
    stack.push(30);

    Iterator<Integer> iterator = stack.iterator();
    while (iterator.hasNext()) {
        System.out.print(iterator.next() + " ");
    }
}

输出结果:

10 20 30

适用场景

  • 需要比基础循环更多灵活性(如遍历时删除元素)

局限

  • 仍不保证LIFO顺序
  • 比简单循环增加复杂度

6. 使用 ListIterator 获取真实LIFO顺序

必须严格按LIFO顺序打印时,ListIterator是最佳方案:

public static void givenStack_whenUsingListIteratorReverseOrder_thenPrintStack() {
    Stack<Integer> stack = new Stack<>();
    stack.push(10);
    stack.push(20);
    stack.push(30);

    ListIterator<Integer> iterator = stack.listIterator(stack.size());
    while (iterator.hasPrevious()) {
        System.out.print(iterator.previous() + " ");
    }
}

输出结果(严格LIFO顺序):

30 20 10

核心优势

  • 不修改栈结构即可正确打印LIFO顺序
  • 专为Stack类设计,避免元素误删

局限

  • 代码量稍多
  • 仅适用于Stack,不兼容Deque

7. 性能最优解:Deque

频繁操作栈时,强烈建议用Deque(如ArrayDeque)替代Stack。它更快、更高效,是现代Java的首选。

public static void givenStack_whenUsingDeque_thenPrintStack() {
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(10);
    stack.push(20);
    stack.push(30);

    stack.forEach(e -> System.out.print(e + " "));
}

输出结果(天然LIFO顺序):

30 20 10

核心优势

  • 性能碾压Stack(尤其高并发场景)
  • 天然保持LIFO顺序
  • 现代Java开发首选
  • 更好的并发支持

⚠️ 迁移成本

  • 需将现有Stack代码重构为Deque

8. 按场景选择最佳方案

根据需求快速决策:

场景 推荐方案
✅ 快速调试 toString()
✅ 自定义格式 增强for循环或Java 8 forEach()
✅ Stack的LIFO顺序 ListIterator
✅ 追求极致性能 Deque(替代Stack)

9. 总结

打印栈值不仅是展示元素,更是选择合适方案的艺术:

  • 快速查看?toString() 足够
  • 需要格式控制?forEach()Iterator 提供灵活性
  • 必须LIFO顺序?ListIterator 是Stack的最佳选择
  • 追求性能?直接用 Deque 替代Stack

最终选择取决于具体场景需求。记住:现代Java中,Deque才是栈操作的正道!

本文所有代码可在 GitHub 获取。


原始标题:Printing Stack Values in Java | Baeldung