基于自定义Link类实现Java队列的技术咨询:差异对比与代码错误排查
问题排查与解决方案
首先,咱们先搞定你遇到的队列元素添加错误——核心问题出在put方法里的一行多余代码,然后再聊聊自定义Link结构和Java原生LinkedList的差异。
一、修复put方法的错误
你当前的put方法里,else块中的first = oldLast;是完全错误的操作:它会导致每次添加新元素时,first指针被强制重置为旧的尾节点,相当于不断把队列的头部往后挪,最终队列里只剩下最后两个有效节点,这就是为什么你只输出了8和9。
修正后的put方法应该是这样的:
public void put(T val) { Link<T> oldLast = last; last = new Link<T>(val); if (first == null) { first = last; // 队列空时,头和尾都指向新节点 } else { oldLast.next = last; // 仅将旧尾节点的next指向新节点,头指针保持不动 } }
简单说:队列的first指针只在初始化(空队列添加第一个元素)时设置,后续添加元素只需要维护尾节点的指向即可,不需要改动头指针。
另外,建议给你的Link类的next字段加上泛型声明,避免编译器的未检查类型警告:
private static class Link<L> { L val; Link<L> next; // 这里加上<L> Link(L val) { this.val = val; this.next = null; } }
还有个小优化:在take方法里,当队列被取空时,把last也置为null,避免内存泄漏,同时保证下次添加元素时的逻辑正确性:
public T take() { T val = null; if (first != null) { val = first.val; first = first.next; // 队列取空后,尾指针也置空 if (first == null) { last = null; } } return val; }
修正后运行你的main方法,就能正常输出0到9的所有数字了。
二、自定义Link结构与JavaLinkedList的差异
咱们从几个核心维度对比:
- 链表类型:你的
Link是单向链表,每个节点只有next指针,只能从头部向后遍历;而JavaLinkedList是双向链表,每个节点同时有prev和next指针,支持头尾双向操作,也能更高效地在中间插入/删除元素。 - 封装与功能丰富度:
LinkedList是一个完整的集合类,实现了List、Deque等接口,提供了offer、poll、addFirst、get等几十种API,还有迭代器、批量操作等功能;你的Link只是一个极简的静态内部类,仅存值和指针,所有操作都依赖外部队列类实现,功能非常基础。 - 线程安全性:两者本身都是非线程安全的,但
LinkedList可以通过Collections.synchronizedList包装,或者用ConcurrentLinkedQueue(基于双向链表的线程安全队列)替代;你的自定义队列需要自己手动加锁(比如ReentrantLock)来实现线程安全。 - 内存开销:单向链表的节点只存
val和next,内存占用更小;双向链表的节点多了一个prev指针,内存开销略大,但换来了更灵活的操作能力。 - 内部实现细节:
LinkedList还维护了size字段,可以直接获取元素数量,你的自定义队列如果需要这个功能,得自己加变量维护;另外LinkedList处理了很多边界情况(比如空指针、并发修改异常等),你的代码需要自己手动处理这些场景。
内容的提问来源于stack exchange,提问作者kim
相关产品推荐
相关产品推荐

