Java 中的 Queue 接口(全类名:java.util.Queue)是一种遵循 FIFO(First-In-First-Out,先进先出)规则的核心数据结构。其核心设计逻辑为:仅允许在队列的尾部(队尾)插入元素,同时仅允许在队列的头部(队首)移除元素 —— 这一特性与日常生活中“超市排队结账”的场景高度契合:先排队的人(先入队元素)优先结算离开(先出队),后续来人(新插入元素)则需在队尾依次等候。
注意,Queue 接口是 Java 集合框架中 Collection 接口的子类型。与 List 类似,它也用于表示一个有序的对象序列,但两者的设计意图存在差异:Queue 更侧重于实现队列这种类具有明确存取规则的数据结构。
Queue 作为 Collection 接口的子类型,Queue 自然继承了 Collection 接口中定义的所有方法(如 add()、remove()、size() 等),因此具备集合框架的基本操作能力。
由于 Queue 是一个接口,你需要实例化该接口的一个具体实现才能使用它。Queue 存在一下实现:
java.util.LinkedList 是 Queue 接口的常用实现类之一,同时也实现了 List 接口。它基于双向链表数据结构实现,因此支持队列的基本操作(如 add() 入队、poll() 出队等),还允许通过索引访问元素。由于链表的特性,LinkedList 在队列首尾进行插入 / 删除操作时效率较高(时间复杂度为 O (1)),但随机访问元素的效率较低(时间复杂度为 O (n))。它适用于需要频繁在队列两端操作,且对元素顺序有严格 FIFO 要求的场景。
java.util.PriorityQueue 是另一种常见的 Queue 实现,其核心特点是不遵循严格的 FIFO 规则,而是根据元素的优先级进行排序。默认情况下,它会按照元素的自然顺序(如数字从小到大、字符串按字典序)排列,也可通过传入 Comparator 自定义排序规则。PriorityQueue 基于二叉堆数据结构实现,每次出队(poll())操作会返回优先级最高的元素,插入操作的时间复杂度为 O (log n),出队操作的时间复杂度为 O (log n)。它适用于需要动态获取“最值”元素的场景,如任务调度中优先执行高优先级任务。
以下是创建 Queue 实例的示例:
// 创建 LinkedList 对象 Queue queueA = new LinkedList(); // 创建 PriorityQueue 对象 Queue queueB = new PriorityQueue();
注意,在大多数队列实现中,队列的头部和尾部位于相对的两端。然而,也可以实现队列接口,使得队列的头部和尾部位于同一端。在这种情况下,你得到的就是一个栈。
默认情况下,你可以将任何对象放入队列中,但从 Java 5 开始,Java 泛型使得限制可以插入队列的对象类型成为可能。例如:
Queue<String> queue = new LinkedList<String>();
现在这个队列只能插入 String 的实例。之后,你可以访问并迭代其元素,而无需对它们进行强制类型转换。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); for(String item : queue) { System.out.println(item); }
注意,增强型 for 循环如何将队列中的每个元素直接转换为 String 实例,因为该队列是以 String 作为泛型类型创建的。
Queue 接口提供了两个用于向队列添加元素的方法:add() 和 offer()。两者的核心功能一致,均是将元素插入队列的尾部(队尾)。
它们的关键区别体现在队列已满(无法容纳新元素)的场景中:
add() 方法会直接抛出 IllegalStateException 异常,明确标示队列已满的错误状态;
offer() 方法则会返回 false,不会抛出异常。
这种设计使得开发者可以根据实际场景选择更合适的处理方式 —— 需要严格校验容量时用 add(),希望避免异常处理时用 offer()。例如:
Queue<String> queue = new LinkedList<>(); queue.add("element 1"); queue.offer("element 2");
注意,LinkedList 本身是 Java 中一个无固定容量限制的集合(基于链表实现),默认情况下会随着元素的添加自动扩容,理论上仅受限于内存大小。因此调用 add() 和 offer() 不会存在队列已满的情况。可直接使用 java.util.concurrent.LinkedBlockingQueue,它本质上是基于链表的有界阻塞队列,构造时可指定容量:
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(10); for(int i = 0; i < 12; i++) { queue.add("item" + i); }
运行上面代码将输出如下错误信息:
Exception in thread "main" java.lang.IllegalStateException: Queue full at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
从 Queue 中取出元素时,可使用 poll() 和 remove() 两种方法 —— 两者均会移除并返回队列的第一个元素(队首元素),区别如下:
若队列为空,poll() 方法会返回 null。
若队列为空,remove() 方法则会抛出 NoSuchElementException 异常。
这种设计允许开发者根据场景选择,需避免异常时用 poll(),需严格校验队列非空状态时用 remove()。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); String element1 = queue.poll(); System.out.println("element1=" + element1); //element1=one String element2 = queue.remove(); System.out.println("element2=" + element2); //element2=two
上述代码,调用 poll() 方法将移除队列的第一个元素 —— 即第一个添加的字符串“one”。调用 remove() 方法将移除队列的第二个元素,在第一次调用 poll() 之后,这个元素现在是添加的 String 字符串“two”。
如果 Queue 为空,执行 poll() 和 remove() 方法效果如下:
Queue<String> queue = new LinkedList<>(); String element1 = queue.poll(); System.out.println("element1=" + element1); // element1=null try { String element2 = queue.remove(); } catch (NoSuchElementException e) { System.out.println("出现异常了, " + e.getMessage()); // 出现异常了, null }
若想查看队列头部的元素而不将其从队列中移除,可使用 Queue 的 element() 或 peek() 方法。
其中,element() 方法会返回队列的第一个元素(队首元素);若队列为空,该方法会直接抛出 NoSuchElementException 异常。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); String firstElement = queue.element(); System.out.println(firstElement); //one
上述代码,firstElement 变量的值为“one”,这是队列(Queue)中的第一个元素。
peek() 方法的作用与 element() 方法类似,不同之处在于如果队列为空,它不会抛出异常,而是返回null。例如:
Queue<String> queue = new LinkedList<>(); queue.add("element 1"); queue.add("element 2"); queue.add("element 3"); String firstElement = queue.peek(); //one
要从 Queue 中移除元素,需调用 remove() 方法,该方法会移除队列头部的元素。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); String removedElement = queue.remove(); System.out.println(removedElement); //one
还可以使用 Queue 的 clear() 方法移除所有元素。实际上,clear() 方法是从 Collection 接口继承而来的。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); queue.clear(); System.out.println(queue); //[]
可以通过 Queue 的 size() 方法读取其存储的元素数量。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); System.out.println(queue.size()); //3
运行上述此代码,将输出 3,表示队列包含 3 个元素。
如果需要检查 Queue 中是否包含某个特定元素,可调用其 contains() 方法。如果 Queue 中存在与目标元素匹配的对象(匹配规则遵循 equals() 方法),则返回 true;若不存在,则返回 false。
注意,contains() 方法并非 Queue 接口原生定义,而是从其父接口 Collection 继承而来的基础方法,但这一实现细节对日常使用几乎无影响,开发者只需关注其 “检查元素是否存在” 的核心功能即可。例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); System.out.println("包含“one”元素?" + queue.contains("one")); //包含“one”元素?true System.out.println("包含“five”元素?" + queue.contains("five")); //包含“five”元素?false
你也可以遍历 Queue 的所有元素,有三种方式进行遍历,分别如下:
(1)使用迭代器 Iterator,例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); Iterator<String> iterator = queue.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); }
(2)使用增强 for 循环,例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); for(String item : queue) { System.out.println(item); }
(3)使用 Stream API,例如:
Queue<String> queue = new LinkedList<>(); queue.add("one"); queue.add("two"); queue.add("three"); queue.stream().forEach(e -> { System.out.println(e); });
查看更多信息见 https://docs.oracle.com/javase/8/docs/api/java/util/Queue.html API 文档。