Интерфейсы Queue и Deque в Java
Положите в очередь LinkedList строки "A", null, "B" и обработайте её привычным циклом while ((s = queue.poll()) != null) — программа выведет только A и молча оставит "B" в очереди. poll() возвращает null и для пустой очереди, и для элемента null, поэтому разница между методами очереди — не формальность.
Queue (в переводе с английского «очередь») — интерфейс java.util.Queue, который описывает очередь: элементы добавляются в хвост (tail) и извлекаются из головы (head), обычно по принципу «первым пришёл — первым ушёл» (FIFO). Deque (double-ended queue, произносится «дек») — интерфейс java.util.Deque, двусторонняя очередь: она расширяет Queue и позволяет добавлять и удалять элементы с обоих концов, поэтому работает и как очередь FIFO, и как стек LIFO.
Интерфейс Queue в Java
Интерфейс Queue<E> появился в Java 5, расширяет Collection и задаёт три операции очереди: добавить в хвост, извлечь из головы и посмотреть голову без удаления. Каждая операция есть в двух вариантах: один бросает исключение, если операция невозможна, другой возвращает специальное значение — false или null.

Большинство очередей работают по FIFO, но интерфейс этого не требует: например, PriorityQueue отдаёт первым не самый ранний, а наименьший элемент. Сама структура данных «очередь» без привязки к Java описана в уроке Структуры данных.
| Операция | Бросает исключение | Возвращает специальное значение |
|---|---|---|
| Добавить в хвост | add(e) — IllegalStateException, если в очереди ограниченной ёмкости нет места | offer(e) — false, если места нет |
| Извлечь из головы (с удалением) | remove() — NoSuchElementException, если очередь пуста | poll() — null, если очередь пуста |
| Посмотреть голову (без удаления) | element() — NoSuchElementException, если очередь пуста | peek() — null, если очередь пуста |
Подробно по каждому методу:
| Метод | Что делает | Пустая или переполненная очередь |
|---|---|---|
boolean add(E e) | Добавляет e в хвост, возвращает true | Нет места — IllegalStateException |
boolean offer(E e) | Пытается добавить e в хвост: true, если элемент добавлен, и false в противном случае | Нет места — false |
E remove() | Удаляет элемент из головы и возвращает его | Пусто — NoSuchElementException |
E poll() | Удаляет элемент из головы и возвращает его | Пусто — null |
E element() | Возвращает элемент из головы, не удаляя его | Пусто — NoSuchElementException |
E peek() | Возвращает элемент из головы, не удаляя его | Пусто — null |
Не путайте remove() без параметров с унаследованным от Collection методом remove(Object o): первый извлекает голову очереди, второй ищет и удаляет конкретный элемент в любом месте. Количество элементов возвращает size(), проверку на пустоту делает isEmpty().
Пример: методы offer(), peek(), poll() и remove()
import java.util.ArrayDeque;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<String> queue = new ArrayDeque<>();
queue.offer("Харьков");
queue.offer("Киев");
queue.offer("Кременчуг");
queue.offer("Львов");
System.out.println("Голова: " + queue.peek());
System.out.println("Размер: " + queue.size());
String town;
while ((town = queue.poll()) != null) {
System.out.println(town);
}
System.out.println("poll() пустой очереди: " + queue.poll());
queue.remove();
}
} Вывод:
Голова: Харьков
Размер: 4
Харьков
Киев
Кременчуг
Львов
poll() пустой очереди: null
Exception in thread "main" java.util.NoSuchElementException Элементы выходят в том же порядке, в каком были добавлены. peek() голову не удаляет, поэтому size() после него по-прежнему 4. На пустой очереди poll() спокойно возвращает null, а remove() завершает программу исключением. Цикл с poll() != null здесь безопасен: ArrayDeque не хранит null, так что null означает только одно — очередь пуста.
Queue и null: когда poll() вводит в заблуждение
Большинство реализаций Queue не допускают null: ArrayDeque, PriorityQueue и очереди из java.util.concurrent бросают NullPointerException при offer(null). Причина в том, что poll() и peek() используют null как сигнал «очередь пуста». Исключение — LinkedList: он принимает null, и тогда этот сигнал становится двусмысленным.
Что выведет этот код?
import java.util.LinkedList;
import java.util.Queue;
public class QueueNullExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("A");
queue.offer(null);
queue.offer("B");
String s;
while ((s = queue.poll()) != null) {
System.out.println(s);
}
System.out.println("Осталось в очереди: " + queue.size());
}
} Вывод:
A
Осталось в очереди: 1 Второй вызов poll() удалил элемент null и вернул его — цикл решил, что очередь закончилась, и "B" остался необработанным. Если в очереди могут быть null, проверяйте пустоту явно:
while (!queue.isEmpty()) {
System.out.println(queue.poll()); // Вывод: A, null, B — каждый с новой строки
} Надёжнее вовсе не класть null в очереди. Если заменить LinkedList на ArrayDeque, ошибка проявится сразу: строка queue.offer(null) бросит NullPointerException.
Интерфейс Deque в Java
Интерфейс Deque<E> появился в Java 6 и расширяет Queue: у двусторонней очереди есть голова (first) и хвост (last), и каждую операцию можно выполнить с любого конца. Методы называются по схеме «операция + First/Last» и, как в Queue, существуют в двух вариантах — с исключением и со специальным значением.

| Метод | Что делает | Пустая или переполненная очередь |
|---|---|---|
void addFirst(E e) | Добавляет e в голову | Нет места в очереди ограниченной ёмкости — IllegalStateException |
void addLast(E e) | Добавляет e в хвост | Нет места — IllegalStateException |
boolean offerFirst(E e) | Пытается добавить e в голову: true, если элемент добавлен, и false в противном случае | Нет места — false |
boolean offerLast(E e) | Пытается добавить e в хвост: true, если элемент добавлен, и false в противном случае | Нет места — false |
E removeFirst() | Удаляет и возвращает первый элемент | Пусто — NoSuchElementException |
E removeLast() | Удаляет и возвращает последний элемент | Пусто — NoSuchElementException |
E pollFirst() | Удаляет и возвращает первый элемент | Пусто — null |
E pollLast() | Удаляет и возвращает последний элемент | Пусто — null |
E getFirst() | Возвращает первый элемент, не удаляя его | Пусто — NoSuchElementException |
E getLast() | Возвращает последний элемент, не удаляя его | Пусто — NoSuchElementException |
E peekFirst() | Возвращает первый элемент, не удаляя его | Пусто — null |
E peekLast() | Возвращает последний элемент, не удаляя его | Пусто — null |
void push(E e) | Стековая операция: то же, что addFirst(e) | Нет места — IllegalStateException |
E pop() | Стековая операция: то же, что removeFirst() | Пусто — NoSuchElementException |
boolean removeFirstOccurrence(Object o) | Удаляет первое вхождение o: true в случае успеха и false, если элемента не было | Пусто — false |
boolean removeLastOccurrence(Object o) | Удаляет последнее вхождение o: true в случае успеха и false, если элемента не было | Пусто — false |
Кроме того, Deque умеет обходить элементы с хвоста: descendingIterator(). С Java 21 Deque расширяет SequencedCollection и получил метод reversed() — представление очереди в обратном порядке (подробнее об этих интерфейсах — в уроке Коллекции в Java). Методы getFirst(), removeFirst() и их пары у Deque были и раньше, начиная с Java 6.
Deque как стек: push, pop, peek
Чтобы использовать Deque как стек, работают только с головой: push() кладёт элемент на вершину, pop() снимает его, peek() показывает вершину без удаления. Чтобы использовать Deque как очередь FIFO, добавляют в хвост (offer()) и забирают из головы (poll()). Все эти методы — синонимы методов с First/Last.
| Назначение | Метод | Эквивалент в Deque |
|---|---|---|
| Очередь (FIFO) | offer(e) / add(e) | offerLast(e) / addLast(e) |
poll() / remove() | pollFirst() / removeFirst() | |
peek() / element() | peekFirst() / getFirst() | |
| Стек (LIFO) | push(e) | addFirst(e) |
pop() | removeFirst() | |
peek() | peekFirst() |
Поскольку и очередь, и стек забирают элементы из головы, их можно смешивать в одном объекте. Что выведет этот код?
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
deque.offer(1); // в хвост
deque.offer(2); // в хвост
deque.push(0); // в голову
System.out.println(deque); // [0, 1, 2]
System.out.println(deque.pop()); // 0 — из головы
System.out.println(deque.pollLast()); // 2 — из хвоста
System.out.println(deque.peek()); // 1 — голова, без удаления
deque.clear();
System.out.println(deque.peek()); // null
deque.pop(); // NoSuchElementException
}
} Вывод:
[0, 1, 2]
0
2
1
null
Exception in thread "main" java.util.NoSuchElementException push(0) поставил ноль перед уже добавленными через offer() элементами, потому что вершина стека и голова очереди — один и тот же конец. На пустом деке peek() возвращает null, а pop() бросает исключение, как removeFirst(). Готовые классы для стека, конструкторы и устройство кольцевого массива разобраны в уроке ArrayDeque, а почему вместо старого класса Stack берут Deque — в уроке Legacy Classes.
У Queue нет метода pop()
Методы push() и pop() объявлены в Deque, а не в Queue. Код Queue<String> q = new ArrayDeque<>(); q.pop(); не скомпилируется (cannot find symbol), хотя сам объект ArrayDeque этот метод имеет: компилятор смотрит на тип переменной. Для очереди используйте poll() или remove(), для стека объявляйте переменную как Deque.
Queue vs Deque: в чём разница
Queue — очередь с одним входом и одним выходом: добавление в хвост, извлечение из головы. Deque — её расширение, двусторонняя очередь, в которой добавлять, извлекать и просматривать элементы можно с обоих концов. Любой Deque является Queue, но не наоборот: PriorityQueue реализует только Queue.
| Признак | Queue | Deque |
|---|---|---|
| Появился | Java 5 | Java 6, расширяет Queue |
| Доступные концы | Добавление в хвост, извлечение из головы | Оба конца для всех операций |
| Порядок | FIFO или по приоритету | FIFO или LIFO — зависит от того, какие методы вызывать |
| Работа как стек | Нет (push()/pop() отсутствуют) | Да: push(), pop(), peek() |
| Основные методы | offer(), poll(), peek() + варианты с исключением | Всё из Queue + offerFirst/Last(), pollFirst/Last(), peekFirst/Last(), descendingIterator() |
| Основные реализации | PriorityQueue, а также все реализации Deque | ArrayDeque, LinkedList, ConcurrentLinkedDeque, LinkedBlockingDeque |
Практическое правило: объявляйте переменную типом Queue, если коду нужна только очередь, и Deque, если нужен стек или доступ к обоим концам. Узкий тип не даст случайно вызвать pollLast() там, где по смыслу допустим только FIFO.
Реализации Queue и Deque
Для обычной очереди или стека в однопоточном коде выбирают ArrayDeque; LinkedList нужен, когда та же коллекция должна быть и списком; PriorityQueue — когда элементы извлекаются по приоритету. Очереди фиксированного размера и потокобезопасные очереди находятся в пакете java.util.concurrent.
| Класс | Интерфейс | null | Ограничение размера | Подробнее |
|---|---|---|---|---|
ArrayDeque | Deque | Нет — NullPointerException | Нет, растёт автоматически | Урок ArrayDeque |
LinkedList | Deque и List | Да | Нет | Урок LinkedList |
PriorityQueue | Queue | Нет — NullPointerException | Нет | Урок PriorityQueue |
ArrayBlockingQueue | BlockingQueue (расширяет Queue) | Нет — NullPointerException | Да, ёмкость задаётся в конструкторе | — |
LinkedBlockingDeque | BlockingDeque (расширяет Deque) | Нет — NullPointerException | Да, если передать ёмкость в конструктор | — |
Именно в очередях ограниченной ёмкости проявляется разница между add() и offer() (и между addFirst() и offerFirst() у Deque). Пример очереди фиксированного размера:
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
public class FixedSizeQueue {
public static void main(String[] args) {
Queue<String> queue = new ArrayBlockingQueue<>(2);
System.out.println(queue.offer("A")); // true
System.out.println(queue.offer("B")); // true
System.out.println(queue.offer("C")); // false — места нет
queue.add("C"); // IllegalStateException
}
} Вывод:
true
true
false
Exception in thread "main" java.lang.IllegalStateException: Queue full У ArrayDeque, LinkedList и PriorityQueue ёмкость не ограничена, поэтому offer() у них всегда возвращает true, а add() никогда не бросает IllegalStateException.
Часто задаваемые вопросы
Как переводится queue и deque и чем deque отличается от dequeue?
Queue — «очередь», deque — сокращение от double-ended queue, «двусторонняя очередь», произносится «дек». Dequeue — другое слово: так называют операцию извлечения элемента из очереди (в Java ей соответствуют poll() и remove()), а добавление называют enqueue (offer(), add()).
Как перебрать очередь, не удаляя элементы?
Циклом for-each или через iterator(): элементы остаются в очереди. У ArrayDeque и LinkedList порядок обхода — от головы к хвосту, а descendingIterator() обходит Deque в обратную сторону. Исключение — PriorityQueue: её итератор не гарантирует порядок по приоритету, отсортированно элементы выходят только через poll().
Можно ли получить элемент очереди по индексу?
Нет: в Queue и Deque нет методов get(int) и add(int, E), доступны только концы. Проверить наличие элемента можно через contains(), удалить из середины — через remove(Object) или removeFirstOccurrence(). Если индексы действительно нужны, берут LinkedList через интерфейс List или ArrayList.
Потокобезопасны ли ArrayDeque и LinkedList?
Нет. Для общей очереди между потоками берут ConcurrentLinkedQueue или ConcurrentLinkedDeque, а для схемы «производитель–потребитель» — BlockingQueue с методами put() и take(), которые ждут места или элемента. Обзор потокобезопасных коллекций — в разделе Потокобезопасные коллекции Java.
Практика по теме
Закрепите урок на задачах — у каждой есть уровень и оценка времени.
Все задачи модуля →Видео объяснение
Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.
Комментарии