前言
我是[提前退休的java猿],一名7年java开发经验的开发组长,分享工作中的各种问题!
在数据结构领域,栈(Stack)是最基础且精妙的概念之一。它遵循 "后进先出"(LIFO,Last-In-First-Out)的原则,类似我们日常叠放盘子、书籍的方式,在编程中也常用于管理函数调用。尽管栈的概念已被广泛理解,但 Java 中栈的实现却有着一段值得每个开发者深入了解的、既有趣又颇具争议的历史。
Java 早在 JDK 1.0 版本就引入了 Stack
类,该类承载着早期的设计决策,而现代开发者在使用时必须谨慎应对这些历史遗留问题。本文将带您深入剖析 Java 栈的实现细节,揭示其隐藏的性能影响,并为您推荐更适合现代应用场景的替代方案。
深入探索 Java 栈:从传统实现到现代方案
一、深入解析 Java Stack 类
1.1 历史背景与设计决策
Java 的 Stack
类继承自 Vector
类,这一决策在 1996 年看似合理,但如今已显得不合时宜。这种继承关系带来了诸多 "包袱",并影响着栈的每一次操作:
java
public class Stack<E> extends Vector<E> {
// Stack 类的实现代码
}
由于继承自 Vector
,Stack
类会继承 Vector
的所有方法,这破坏了栈的封装性。开发者可以通过索引访问栈中的元素、在栈中间插入元素,甚至执行其他违反 "后进先出" 原则的操作:
java
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
// 在纯栈结构中,以下操作本应不被允许!
stack.add(1, 99); // 在索引 1 的位置插入 99
System.out.println(stack); // 输出结果:[1, 99, 2, 3]
1.2 线程安全:一把双刃剑
Stack
类继承了 Vector
的同步方法,默认具备线程安全特性,但这也带来了巨大的性能代价:
java
public synchronized E push(E item) {
addElement(item);
return item;
}
public synchronized E pop() {
E obj = peek();
removeElementAt(size() - 1);
return obj;
}
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
每一次栈操作都会获取锁,即便在单线程环境中,这种锁机制也是多余的。与非同步的替代方案相比,这种同步开销可能会导致性能下降 2-3 倍。
1.3 方法解析与内部机制
我们来详细分析 Stack
类的核心方法:
push (E item) 方法
java
public synchronized E push(E item) {
addElement(item);
return item;
}
- 时间复杂度:均摊 O (1);最坏情况下 O (n)(当数组需要扩容时)
- 空间复杂度:O(1)
- 功能特点:返回被压入栈的元素,方便进行链式操作
pop () 方法
java
public synchronized E pop() {
E obj = peek();
removeElementAt(size() - 1);
return obj;
}
- 时间复杂度:O(1)
- 异常情况 :若栈为空,会抛出
EmptyStackException
- 内部逻辑 :先调用
peek()
方法获取栈顶元素,再移除该元素
peek () 方法
java
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
- 时间复杂度:O(1)
- 功能特点:获取栈顶元素但不将其从栈中移除
- 异常情况 :若栈为空,会抛出
EmptyStackException
search (Object o) 方法
java
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
- 时间复杂度:O(n)
- 返回值规则:返回元素相对于栈顶的位置(基于 1 开始计数,而非 0 开始)
- 未找到元素:返回 -1
1.4 内部实现细节
Stack
类通过继承 Vector
类,使用其内部数组存储元素:
java
// 来自 Vector 类的代码
protected Object[] elementData;
protected int elementCount;
当数组已满时,Vector
会采用一套复杂的扩容策略(JDK 17 及以上版本):
java
// Vector 类的现代扩容策略(JDK 17+)
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* 最小扩容幅度 */
capacityIncrement > 0 ? capacityIncrement : oldCapacity
/* 首选扩容幅度 */);
return elementData = Arrays.copyOf(elementData, newCapacity);
}
扩容行为分析
-
当
capacityIncrement = 0
(默认值)时:"首选扩容幅度" 参数会设为oldCapacity
,即数组容量倾向于翻倍 -
当
capacityIncrement > 0
时:会使用指定的增量作为首选扩容幅度 -
最终决策逻辑:
ArraysSupport.newLength()
方法会将 "首选扩容幅度" 作为参考,但会根据 JVM 约束、内存限制和溢出保护机制对扩容幅度进行调整
当 capacityIncrement = 0
时的 "翻倍扩容" 策略,延续了传统的几何级数扩容模式,能保证插入操作的均摊时间复杂度为 O (1);而 ArraysSupport.newLength()
方法则为其增加了现代的安全性和优化层。
二、现代替代方案
2.1 ArrayDeque:推荐首选方案
ArrayDeque
(Java 6 引入)是用于栈操作的现代首选实现:
java
import java.util.ArrayDeque;
import java.util.Deque;
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); // 等效于 addFirst() 方法
stack.push(2); // 等效于 addFirst() 方法
stack.push(3); // 等效于 addFirst() 方法
Integer top = stack.pop(); // 等效于 removeFirst() 方法
Integer peek = stack.peek(); // 等效于 peekFirst() 方法
ArrayDeque 的优势
- 无同步开销,性能更优
- 内存使用效率更高
- 实现了 Deque 接口(双端队列),功能更灵活
- 性能特性更出色
- 无容量限制,可根据需求动态扩容
2.2 用 LinkedList 实现栈
尽管性能不如 ArrayDeque
,但 LinkedList
也可用于实现栈功能:
java
LinkedList<Integer> stack = new LinkedList<>();
stack.push(1); // 等效于 addFirst() 方法
stack.push(2); // 等效于 addFirst() 方法
Integer top = stack.pop(); // 等效于 removeFirst() 方法
适合使用 LinkedList 的场景
- 需要在两端都能实现常数时间复杂度的插入 / 删除操作时
- 内存资源比 CPU 性能更紧张时
- 已在其他操作中使用
LinkedList
,为保持一致性而选择时
2.3 自定义栈实现
出于学习目的或满足特定需求,开发者也可以自行实现栈:
java
import java.util.Arrays;
import java.util.EmptyStackException;
public class CustomStack<T> {
private static final int DEFAULT_CAPACITY = 10;
private Object[] array;
private int size;
public CustomStack() {
array = new Object[DEFAULT_CAPACITY];
size = 0;
}
public void push(T item) {
ensureCapacity();
array[size++] = item;
}
@SuppressWarnings("unchecked")
public T pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
T item = (T) array[--size];
array[size] = null; // 帮助垃圾回收
return item;
}
@SuppressWarnings("unchecked")
public T peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return (T) array[size - 1];
}
public boolean isEmpty() {
return size == 0;
}
public int size() {
return size;
}
private void ensureCapacity() {
if (size == array.length) {
array = Arrays.copyOf(array, array.length * 2);
}
}
}
三、性能分析
3.1 综合基准测试结果
以下是不同栈实现的性能对比代码:
java
public class StackBenchmark {
private static final int OPERATIONS = 1_000_000; // 操作次数
public static void main(String[] args) {
benchmarkPushPop();
}
private static void benchmarkPushPop() {
// 测试传统 Stack 类
long startTime = System.nanoTime();
Stack<Integer> legacyStack = new Stack<>();
for (int i = 0; i < OPERATIONS; i++) {
legacyStack.push(i);
}
for (int i = 0; i < OPERATIONS; i++) {
legacyStack.pop();
}
long legacyTime = System.nanoTime() - startTime;
// 测试 ArrayDeque
startTime = System.nanoTime();
Deque<Integer> arrayDeque = new ArrayDeque<>();
for (int i = 0; i < OPERATIONS; i++) {
arrayDeque.push(i);
}
for (int i = 0; i < OPERATIONS; i++) {
arrayDeque.pop();
}
long arrayDequeTime = System.nanoTime() - startTime;
// 输出测试结果
System.out.printf("传统 Stack 类:%d 毫秒%n", legacyTime / 1_000_000);
System.out.printf("ArrayDeque:%d 毫秒%n", arrayDequeTime / 1_000_000);
System.out.printf("性能提升倍数:%.2f 倍%n",
(double) legacyTime / arrayDequeTime);
}
}
典型测试结果
- 传统 Stack 类:约 850 毫秒
- ArrayDeque:约 280 毫秒
- 性能提升倍数:3.04 倍
3.2 内存开销分析
1. Stack(继承自 Vector)
- 对象头:8-16 字节
- Vector 类字段:约 32 字节
- 数组开销:16 字节 + 每个元素 4 字节
- 每个元素的总开销:约 52-56 字节
2. ArrayDeque
- 对象头:8-16 字节
- ArrayDeque 类字段:约 16 字节
- 数组开销:16 字节 + 每个元素 4 字节
- 每个元素的总开销:约 40-44 字节
3.3 时间复杂度对比
操作方法 | Stack | ArrayDeque | LinkedList |
---|---|---|---|
push() | O(1)* | O(1)* | O(1) |
pop() | O(1) | O(1) | O(1) |
peek() | O(1) | O(1) | O(1) |
search() | O(n) | O(n) | O(n) |
size() | O(1) | O(1) | O(1) |
注:* 表示均摊时间复杂度为 O (1),最坏情况下(数组扩容时)为 O (n)
3.4 对垃圾回收的影响
传统 Stack 类因继承自 Vector,可能导致更频繁的垃圾回收暂停:
- Stack 每次扩容会增加 100% 容量,可能造成内存浪费
- ArrayDeque 每次扩容仅增加 50% 容量,内存效率更高
- 这一差异会影响垃圾回收的频率和暂停时间
实现案例和行业应用
一、实际实现案例
1.1 表达式求值(中缀转后缀)
java
import java.util.ArrayDeque;
import java.util.Deque;
public class ExpressionEvaluator {
public static String infixToPostfix(String expression) {
Deque<Character> stack = new ArrayDeque<>();
StringBuilder result = new StringBuilder();
for (char c : expression.toCharArray()) {
// 若为数字,直接加入结果
if (Character.isDigit(c)) {
result.append(c);
}
// 若为左括号,压入栈中
else if (c == '(') {
stack.push(c);
}
// 若为右括号,弹出栈中元素直到遇到左括号
else if (c == ')') {
while (!stack.isEmpty() && stack.peek() != '(') {
result.append(stack.pop());
}
stack.pop(); // 移除左括号
}
// 若为运算符,按优先级处理
else if (isOperator(c)) {
while (!stack.isEmpty() && precedence(c) <= precedence(stack.peek())) {
result.append(stack.pop());
}
stack.push(c);
}
}
// 弹出栈中剩余的运算符
while (!stack.isEmpty()) {
result.append(stack.pop());
}
return result.toString();
}
// 判断是否为运算符
private static boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
// 定义运算符优先级
private static int precedence(char operator) {
switch (operator) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
}
1.2 括号匹配检查器
java
import java.util.ArrayDeque;
import java.util.Deque;
public class ParenthesesChecker {
public static boolean isBalanced(String expression) {
Deque<Character> stack = new ArrayDeque<>();
for (char c : expression.toCharArray()) {
// 遇到左括号,压入栈中
if (isOpenBracket(c)) {
stack.push(c);
}
// 遇到右括号,检查是否与栈顶左括号匹配
else if (isCloseBracket(c)) {
if (stack.isEmpty()) {
return false; // 无匹配的左括号
}
char open = stack.pop();
if (!isMatchingPair(open, c)) {
return false; // 括号类型不匹配
}
}
}
// 若栈为空,说明所有括号都匹配
return stack.isEmpty();
}
// 判断是否为左括号
private static boolean isOpenBracket(char c) {
return c == '(' || c == '[' || c == '{';
}
// 判断是否为右括号
private static boolean isCloseBracket(char c) {
return c == ')' || c == ']' || c == '}';
}
// 判断括号是否匹配
private static boolean isMatchingPair(char open, char close) {
return (open == '(' && close == ')') ||
(open == '[' && close == ']') ||
(open == '{' && close == '}');
}
}
1.3 撤销功能实现
java
import java.util.ArrayDeque;
import java.util.Deque;
public class UndoRedoManager<T> {
private final Deque<Command<T>> undoStack; // 用于存储可撤销的命令
private final Deque<Command<T>> redoStack; // 用于存储可重做的命令
public UndoRedoManager() {
this.undoStack = new ArrayDeque<>();
this.redoStack = new ArrayDeque<>();
}
// 执行命令并将其加入撤销栈
public void execute(Command<T> command) {
command.execute();
undoStack.push(command);
redoStack.clear(); // 执行新命令后,清空重做栈
}
// 撤销上一步操作
public boolean undo() {
if (undoStack.isEmpty()) {
return false; // 无操作可撤销
}
Command<T> command = undoStack.pop();
command.undo();
redoStack.push(command); // 将撤销的命令加入重做栈
return true;
}
1.4 递归算法的调用栈模拟
java
public class IterativeFactorial {
public static long factorial(int n) {
if (n < 0) throw new IllegalArgumentException("Negative numbers not allowed");
if (n <= 1) return 1;
Deque<Integer> stack = new ArrayDeque<>();
long result = 1;
// Simulate recursive calls by pushing onto stack
while (n > 1) {
stack.push(n);
n--;
}
// Simulate unwinding by popping and calculating
while (!stack.isEmpty()) {
result *= stack.pop();
}
return result;
}
// Tree traversal without recursion
public static void iterativeInorderTraversal(TreeNode root) {
if (root == null) return;
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode current = root;
while (current != null || !stack.isEmpty()) {
// Go to the leftmost node
while (current != null) {
stack.push(current);
current = current.left;
}
// Process current node
current = stack.pop();
System.out.print(current.val + " ");
// Move to right subtree
current = current.right;
}
}
}
二、行业应用场景与实际案例
2.1 Web 开发与 HTTP 请求处理
请求 / 响应栈管理
java
public class HttpRequestProcessor {
private final Deque<HttpContext> contextStack = new ArrayDeque<>();
public void processRequest(HttpServletRequest request, HttpServletResponse response) {
HttpContext context = new HttpContext(request, response);
contextStack.push(context);
try {
// 处理过滤器、中间件、控制器
processMiddleware();
processController();
} finally {
contextStack.pop(); // 确保上下文始终被清理
}
}
public HttpContext getCurrentContext() {
return contextStack.isEmpty() ? null : contextStack.peek();
}
}
应用场景:Spring 框架、Jersey 等 Web 框架均采用栈式结构管理请求上下文、过滤器链和中间件处理流程。
2.2编译器设计与代码解析
IDE中的语法分析
java
public class JavaSyntaxAnalyzer {
private final Deque<SyntaxElement> syntaxStack = new ArrayDeque<>();
public boolean validateSyntax(String javaCode) {
TokenStream tokens = tokenize(javaCode);
for (Token token : tokens) {
switch (token.getType()) {
case OPEN_BRACE:
case OPEN_PAREN:
case OPEN_BRACKET:
syntaxStack.push(new SyntaxElement(token));
break;
case CLOSE_BRACE:
case CLOSE_PAREN:
case CLOSE_BRACKET:
if (syntaxStack.isEmpty() ||
!isMatchingPair(syntaxStack.pop(), token)) {
return false; // 语法错误
}
break;
case IF:
case WHILE:
case FOR:
syntaxStack.push(new SyntaxElement(token));
break;
}
}
return syntaxStack.isEmpty(); // 所有语法结构都应正确闭合
}
}
行业应用:
- IntelliJ IDEA、Eclipse、VS Code 等 IDE 利用栈实现语法高亮和错误检测
- javac 等编译器借助栈完成代码解析与语义分析
- 代码格式化工具通过栈跟踪缩进层级
2.3 金融交易系统
订单管理与风险控制
java
public class TradingEngine {
private final Deque<TradeOrder> pendingOrders = new ArrayDeque<>();
private final Deque<RiskCheck> riskStack = new ArrayDeque<>();
public void processOrder(TradeOrder order) {
// 基于栈的风险验证
riskStack.push(new PositionRisk(order));
riskStack.push(new CreditRisk(order));
riskStack.push(new MarketRisk(order));
// 反向顺序(LIFO)验证风险
while (!riskStack.isEmpty()) {
RiskCheck risk = riskStack.pop();
if (!risk.validate()) {
cancelOrder(order, risk.getReason());
return;
}
}
executeOrder(order);
}
// 订单取消操作利用栈实现正确的回滚逻辑
public void cancelNestedOrders(String parentOrderId) {
Deque<TradeOrder> cancellationStack = new ArrayDeque<>();
// 查找所有子订单并压入栈中
findChildOrders(parentOrderId, cancellationStack);
// 按依赖关系反向取消订单
while (!cancellationStack.isEmpty()) {
cancelOrder(cancellationStack.pop());
}
}
}
行业应用:高盛、摩根大通等金融机构的交易系统,均采用栈式结构处理订单、管理风险及执行交易回滚。
2.4 游戏开发与图形学
场景图管理
java
public class SceneRenderer {
private final Deque<Matrix4f> transformationStack = new ArrayDeque<>();
private final Deque<RenderState> stateStack = new ArrayDeque<>();
public void renderScene(SceneNode node) {
// 压入当前变换矩阵
transformationStack.push(getCurrentTransformation());
stateStack.push(getCurrentRenderState());
// 应用节点的变换
applyTransformation(node.getTransform());
applyRenderState(node.getRenderState());
// 渲染当前节点
renderNode(node);
// 递归渲染子节点
for (SceneNode child : node.getChildren()) {
renderScene(child);
}
// 恢复之前的渲染状态
restoreTransformation(transformationStack.pop());
restoreRenderState(stateStack.pop());
}
}
行业应用:
- Unity、Unreal Engine 等游戏引擎利用栈实现场景图遍历
- OpenGL/DirectX 图形接口的状态管理
- GPU 着色器执行栈
2.5 数据库管理系统
事务处理
java
public class TransactionManager {
private final Deque<TransactionFrame> transactionStack = new ArrayDeque<>();
private final Deque<UndoLogEntry> undoStack = new ArrayDeque<>();
public void beginTransaction() {
TransactionFrame frame = new TransactionFrame(generateTxnId());
transactionStack.push(frame);
}
public void executeOperation(DatabaseOperation operation) {
if (transactionStack.isEmpty()) {
throw new IllegalStateException("无活跃事务");
}
// 执行前创建回滚日志条目
UndoLogEntry undoEntry = operation.createUndoEntry();
undoStack.push(undoEntry);
try {
operation.execute();
transactionStack.peek().addOperation(operation);
} catch (Exception e) {
// 执行失败时自动回滚
undoStack.pop().undo();
throw e;
}
}
public void rollback() {
TransactionFrame currentTxn = transactionStack.peek();
// 反向顺序(LIFO)回滚操作
while (!undoStack.isEmpty() &&
undoStack.peek().getTransactionId().equals(currentTxn.getId())) {
undoStack.pop().undo();
}
transactionStack.pop();
}
}
行业应用:Oracle、PostgreSQL、MySQL 等数据库系统,通过栈实现事务管理、查询执行计划及回滚操作。
2.6 DevOps 与容器编排
部署流水线管理
java
public class DeploymentPipeline {
private final Deque<DeploymentStage> executionStack = new ArrayDeque<>();
private final Deque<RollbackAction> rollbackStack = new ArrayDeque<>();
public void deploy(Application app, Environment target) {
try {
// 压入部署阶段
executionStack.push(new DatabaseMigration(app));
executionStack.push(new ServiceDeployment(app));
executionStack.push(new LoadBalancerUpdate(target));
executionStack.push(new HealthCheck(app, target));
// 执行部署阶段
while (!executionStack.isEmpty()) {
DeploymentStage stage = executionStack.pop();
RollbackAction rollback = stage.execute();
rollbackStack.push(rollback);
}
} catch (DeploymentException e) {
// 部署失败时自动反向回滚
rollbackDeployment();
throw e;
}
}
private void rollbackDeployment() {
while (!rollbackStack.isEmpty()) {
try {
rollbackStack.pop().execute();
} catch (Exception e) {
logger.error("回滚失败", e);
}
}
}
}
行业应用:
- Kubernetes 利用栈实现资源管理与回滚
- Jenkins、GitLab CI 通过栈管理流水线执行
- Docker 的镜像层管理采用栈式结构
- Terraform 借助栈解析资源依赖关系
2.7 内存管理与垃圾回收
JVM 栈帧管理
java
// JVM如何管理方法调用的概念性实现
public class JVMStackSimulation {
private final Deque<StackFrame> callStack = new ArrayDeque<>();
public Object invokeMethod(Method method, Object[] args) {
// 创建新栈帧
StackFrame frame = new StackFrame(method, args);
callStack.push(frame);
try {
// 执行方法字节码
Object result = executeMethod(frame);
return result;
} catch (Exception e) {
// 异常处理时的栈展开
unwindStack(e);
throw e;
} finally {
// 确保栈帧始终被弹出
callStack.pop();
}
}
private void unwindStack(Exception e) {
while (!callStack.isEmpty()) {
StackFrame frame = callStack.peek();
if (frame.canHandle(e)) {
break; // 找到异常处理器
}
callStack.pop(); // 展开当前栈帧
}
}
}
行业应用:所有 JVM 实现均通过栈管理方法调用、异常处理及垃圾回收根节点扫描。
2.8 性能监控与应用性能监控(APM)
分布式追踪
java
public class TracingContext {
private final Deque<Span> spanStack = new ArrayDeque<>();
public void startSpan(String operationName) {
Span parentSpan = spanStack.isEmpty() ? null : spanStack.peek();
Span newSpan = createSpan(operationName, parentSpan);
spanStack.push(newSpan);
}
public void finishSpan() {
if (!spanStack.isEmpty()) {
Span span = spanStack.pop();
span.finish();
// 发送至APM系统(Datadog、New Relic等)
tracingReporter.report(span);
}
}
public void recordException(Exception e) {
if (!spanStack.isEmpty()) {
spanStack.peek().recordException(e);
}
}
}
行业应用:
- Datadog、New Relic、AppDynamics 等 APM 工具利用栈实现分布式追踪
- OpenTelemetry 规范基于跨度栈(Span Stack)设计
- 性能分析工具通过调用栈进行性能分析
最佳实践与建议:各类实现的适用场景
选择 ArrayDeque 的场景
- 追求极致性能时
- 单线程环境下
- 开发现代 Java 应用(Java 6 及以上版本)
- 执行通用栈操作时
选择 Collections.synchronizedDeque () 的场景
- 多线程环境下
- 需要显式控制同步逻辑时
- 线程安全优先级高于性能时
避免使用传统 Stack 类的场景
- 开发新应用时
- 对性能有要求时
- 追求代码简洁可维护时
- 遵循现代 Java 最佳实践时
需规避的常见陷阱
- 在新代码中使用传统 Stack 类
- 执行 pop ()/peek () 前未检查栈是否为空
- 使用 search () 方法时误将返回值当作 0-based 索引
- 将栈操作与继承自 Vector 的方法混用
- 未根据实际需求考虑线程安全特性
总结
Java 的 Stack 类是 "历史设计决策影响现代开发" 的典型案例。尽管它在 Java 早期版本中提供了可用的栈实现,但继承自 Vector 的设计及同步开销,使其不再适用于当代应用场景。
现代 Java 开发者的核心收获:
- 单线程环境下,优先使用 ArrayDeque 实现栈操作
- 理解同步机制对性能的影响
- 根据具体需求选择合适的实现方案
- 从 legacy 代码中汲取经验,做出更优的设计决策
深入理解这些实现,不仅是学习栈这一数据结构,更能洞察 API 设计、性能优化及编程语言的演进规律。栈虽为简单概念,但其实现背后蕴含着丰富的软件工程原理与权衡思路。
下次在 Java 中需要使用栈时,您将明确知道该选择哪种实现及背后的原因。更重要的是,您将理解 Java 从早期版本到现代高性能版本的演进历程。
继续探索数据结构与算法吧!每一种数据结构都有其独特的演进、优化及实际应用故事,等待我们去发现。
➡今天这篇文章翻译:In-Depth Java Stack Exploration: From Legacy to Modern Implementation