Java中应避免使用Stack类,因其继承Vector导致同步开销大、API语义混乱、扩容成本高;推荐用ArrayDeque实现LIFO,性能高3–5倍,线程安全场景选ConcurrentLinkedDeque。

Java里用Stack类基本等于给自己埋雷
别用Stack类,它被标记为@Deprecated不是因为过时,而是设计错误:继承Vector导致同步开销大、API语义混乱(比如push()和add()都可用但行为不一致),且底层是数组,扩容成本高。
真实场景中,你真正需要的是LIFO行为——直接用ArrayDeque代替:
Deque<String> stack = new ArrayDeque<>();
stack.push("a"); // ✅ 推荐
stack.pop(); // ✅ 推荐
// 别写 stack.add("a") 或 stack.elementAt(0)
-
ArrayDeque非线程安全,但绝大多数业务不需要同步,性能比Stack高3–5倍 - 如果真要线程安全,用
ConcurrentLinkedDeque,而不是Stack加synchronized -
Stack的search()方法返回从栈顶起的位置,但实际没人靠这个索引做逻辑——说明API偏离使用本质
Queue接口的实现选哪个?看你是要阻塞还是非阻塞
Java里Queue是接口,具体行为取决于实现类。别一上来就用LinkedList,它虽然实现了Queue,但内部是双向链表,随机访问慢,且没有容量限制,OOM风险隐蔽。
按场景选:
立即学习“Java免费学习笔记(深入)”;
- 普通缓存/任务暂存 →
ArrayDeque:数组实现,O(1)头尾操作,内存连续,GC压力小 - 生产者-消费者模型(比如消息队列)→
LinkedBlockingQueue:带容量限制,put()/take()会阻塞,适合控制吞吐节奏 - 高并发无锁场景 →
ConcurrentLinkedQueue:CAS实现,不阻塞也不锁,但size()不准(遍历计数),慎用于依赖精确长度的逻辑
注意:PriorityQueue不是FIFO,它是堆实现的优先级队列,poll()返回最小元素——别误当普通队列用。
为什么offer()、add()、poll()、remove()四个方法总让人懵
这是Queue接口故意设计的两套语义:一套失败返回false/null(offer、poll),一套失败抛IllegalStateException(add、remove)。区别只在“是否接受失败”。
- 有界队列(如
LinkedBlockingQueue设了容量):用offer()和poll(),避免因满/空触发异常 - 无界队列(如
ArrayDeque):add()和remove()理论上不会失败,但代码可读性差——不如统一用offer()/poll()保持风格一致 -
element()和peek()同理:peek()空时返回null,element()空时抛NoSuchElementException
真实项目里,95%的情况该用offer()和poll(),因为“失败是常态”,不是bug。
泛型擦除后,怎么安全地从Queue里取特定类型对象
泛型只在编译期检查,运行时类型信息没了。所以别信queue.poll() instanceof SomeType这种写法——如果队列里混入了其他类型,ClassCastException会在下游某个cast点爆发,很难定位。
- 最稳的方式:定义专用容器类,把
Queue<T>封装起来,在offer()入口做instanceof校验(或用Objects.requireNonNull配合文档约定) - 如果必须动态判断类型,用
Optional<T> tryPollAs(Class<T> type)模式,内部用getClass().isAssignableFrom()比instanceof更可控 - 千万别在
poll()后无脑强转:(SomeType) queue.poll()——泛型擦除后这行代码在字节码里就是裸checkcast,错在运行时
类型安全不是靠编译器单方面保证的,得靠约束入口 + 明确契约。这点在多模块协作时尤其关键——别人往你队列里塞东西,你不能假设他看了你的泛型声明。










