Queue.poll: примеры (JAVA)

Разбор поведения метода poll в очередях Java
Раздел: Коллекции (Collection Framework) - Queue/Deque
Queue.poll: E

Описание метода Queue.poll

В Java метод poll() принадлежит интерфейсу java.util.Queue. Он извлекает и возвращает головной элемент очереди, или возвращает null, если очередь пуста. Метод не выбрасывает исключение при пустой очереди, что отличает его от remove().

Подписи и варианты:

  • E poll() - стандартный метод интерфейса Queue. Возвращает головной элемент и удаляет его из очереди; при пустой очереди возвращает null.
  • E poll(long timeout, java.util.concurrent.TimeUnit unit) throws InterruptedException - перегруженный метод в интерфейсе java.util.concurrent.BlockingQueue. Пытается извлечь элемент в течение заданного времени, возвращает null, если таймаут истёк и элемент не получен; может прерываться и бросить InterruptedException.
  • В интерфейсе Deque доступны варианты pollFirst() и pollLast() для операций с обоих концов двунаправленной очереди.

Возвращаемые значения и поведение:

  • Возвращает элемент типа E - голову очереди, после чего этот элемент удаляется.
  • При пустой очереди возвращает null (в стандартном poll()). Следует учитывать, что null может быть значением, отличающимся от «отсутствия элемента» только если реализация позволяет хранить null - но большинство реализаций очередей не допускают null.
  • В блокирующем варианте с таймаутом возвращает null при истечении времени ожидания; при прерывании потока бросается InterruptedException.

Когда используется:

  • Когда требуется извлечь и удалить элемент из очереди без риска выбрасывания исключения при пустой структуре.
  • В многопоточных сценарииях удобно сочетать poll() с проверками на null для немедленной неблокирующей обработки.
  • В случаях ожидания доступного элемента в течение ограниченного времени применяется BlockingQueue.poll(timeout, unit).

Короткие примеры использования poll

Пример 1. LinkedList как Queue

import java.util.*;

Queue<String> q = new LinkedList<>();
q.add("a");
q.add("b");
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
a
b
null

Пример 2. ArrayDeque

Queue<Integer> q = new ArrayDeque<>();
q.offer(1);
q.offer(2);
System.out.println(q.poll());
System.out.println(q.peek());
1
2

Пример 3. PriorityQueue

Queue<Integer> pq = new PriorityQueue<>>();
pq.add(10);
pq.add(3);
pq.add(7);
System.out.println(pq.poll());
System.out.println(pq.poll());
System.out.println(pq.poll());
3
7
10

Пример 4. BlockingQueue с таймаутом

import java.util.concurrent.*;

BlockingQueue<String> bq = new ArrayBlockingQueue<>(2);
bq.offer("x");
try {
    String s = bq.poll(500, TimeUnit.MILLISECONDS);
    System.out.println(s);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("interrupted");
}
x

Похожие методы в Java и их особенности

  • remove() - извлекает голову очереди, но при пустой структуре бросает NoSuchElementException. Предпочтительнее, когда пустая очередь считается ошибкой.
  • peek() - возвращает голову без удаления, при пустой очереди возвращает null. Удобен для проверки следующего элемента без изменения очереди.
  • poll(long, TimeUnit)BlockingQueue) - блокирующий вариант с таймаутом и возможностью прерывания. Подходит для ожидания в многопоточном окружении.
  • take()BlockingQueue) - блокирует до получения элемента, бросает InterruptedException. Используется, когда требуется гарантированное получение.
  • pollFirst()/pollLast()Deque) - извлечение с определённого конца. Применяется при реализации двунаправленной очереди.

Эквиваленты в других языках и отличия

PHP

$q = new SplQueue();
$q->enqueue('a');
$q->enqueue('b');
echo $q->dequeue();
echo PHP_EOL;
echo $q->dequeue();
a
b

Особенности: dequeue() бросает RuntimeException при пустой очереди; поведения по умолчанию нет варианта, возвращающего null.

JavaScript

const q = ['a','b'];
console.log(q.shift());
console.log(q.shift());
console.log(q.shift());
a
b
undefined

Особенности: Array.shift() возвращает undefined на пустом массиве. Операция O(n) для массивов, для производительных очередей рекомендуются двусвязные списки или специализированные структуры.

Python

from collections import deque
q = deque(['a','b'])
print(q.popleft())
print(q.popleft())

# queue.Queue для потокобезопасного варианта
import queue
bq = queue.Queue()
bq.put('x')
print(bq.get_nowait())
a
b
x

Особенности: deque.popleft() быстрое O(1); queue.Queue.get() может блокировать или бросать исключение при использовании get_nowait().

SQL

-- концепт: выбрать и удалить одну строку в порядке FIFO
BEGIN TRANSACTION;
SELECT id, payload FROM messages ORDER BY created_at LIMIT 1;
DELETE FROM messages WHERE id = ?;
COMMIT;
-- результат зависит от данных в таблице

Особенности: операции требуют транзакций для атомарности; нет встроенного метода poll.

C#

var q = new Queue<string>();
q.Enqueue("a");
q.Enqueue("b");
Console.WriteLine(q.Count > 0 ? q.Dequeue() : null);

// ConcurrentQueue в .NET имеет TryDequeue
var cq = new System.Collections.Concurrent.ConcurrentQueue<string>();
cq.Enqueue("x");
if (cq.TryDequeue(out var v)) Console.WriteLine(v);
a
x

Особенности: Dequeue() бросает InvalidOperationException при пустой очереди; в многопоточном варианте доступны TryDequeue.

Go

// использование канала
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
// non-blocking receive
select {
case v := <-ch:
    fmt.Println(v)
default:
    fmt.Println("empty")
}
1
2

Особенности: каналы обеспечивают блокирующее и неблокирующее чтение через select; для структуры списка можно использовать container/list.

Kotlin

val q: java.util.Queue<String> = java.util.ArrayDeque()
q.add("a")
println(q.poll())
println(q.poll())
a
null

Особенности: Kotlin использует те же реализации, что и Java; сигнатуры и поведение аналогичны.

Lua

local q = { 'a', 'b' }
print(table.remove(q, 1))
print(table.remove(q, 1))
print(table.remove(q, 1))
a
b
nil

Особенности: table.remove с индексом 1 реализует pop-перед, но сдвигает все элементы - менее эффективно для больших очередей.

Типичные ошибки при использовании poll

  • Ожидание исключения вместо проверки null. Многие примеры предполагают, что poll() бросает исключение при пустой очереди, тогда как он возвращает null. Пример ошибки:
Queue<String> q = new LinkedList<>();
String s = q.poll();
// Ошибка: если считать, что s нельзя сравнивать с null без проверки
System.out.println(s.length());
Exception in thread "main" java.lang.NullPointerException
	at ...
  • Использование poll() вместо блокирующего метода в многопоточной логике, где нужно дождаться элемента. Это может приводить к постоянному получению null и бессмысленной работе цикла.
  • Игнорирование InterruptedException в poll(timeout,...). Неправильная обработка прерывания приводит к непредсказуемому поведению потоков.
  • Предположение о порядке элементов в PriorityQueue. При использовании приоритетной очереди извлекается наименьший элемент по сравнению, а не в порядке вставки.
  • Попытка хранить null в качестве элемента. Большинство реализаций очередей не допускают null, и вставка может бросить NullPointerException.
  • Неправильная синхронизация при использовании несинхронизированных реализаций в многопоточном окружении - лучше применять concurrent-реализации.

Изменения в методе в последних версиях Java

Метод poll() как таковой интерфейса Queue остался стабильным и не подвергался существенным изменениям в последних версиях языка. Значимые дополнения в экосистеме:

  • С момента появления пакета java.util.concurrent появились расширения: блокирующий вариант poll(timeout, unit) в BlockingQueue, а также новые concurrent-реализации очередей.
  • Ввод default-методов в интерфейсах коллекций не затронул базовое поведение poll().

Вывод: явных изменений в семантике метода в современных релизах Java нет, но развивались и добавлялись специализированные реализации и методы для конкурентных сценариев.

Расширенные примеры и нестандартные сценарии

1) Дрейн очереди в пакетах для обработки

Пример java
Queue<String> q = new ConcurrentLinkedQueue<>();
q.add("a");
q.add("b");
q.add("c");
int maxBatch = 2;
List<String> batch = new ArrayList<>();
String item;
while ((item = q.poll()) != null && batch.size() < maxBatch) {
    batch.add(item);
}
System.out.println(batch);
System.out.println(q);
[a, b]
[c]

Пояснение: позволяет обрабатывать элементы пакетами без блокирования, сохраняя оставшиеся элементы в очереди.

2) Producer-consumer с таймаутом и корректной обработкой прерываний

Пример java
import java.util.concurrent.*;

BlockingQueue<String> q = new ArrayBlockingQueue<>(10);
// consumer
Runnable consumer = () -> {
    try {
        while (true) {
            String s = q.poll(1, TimeUnit.SECONDS);
            if (s == null) {
                System.out.println("timed out, shutdown check");
                break; // пример остановки при простое
            }
            System.out.println("consumed " + s);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
};

// producer
q.offer("task1");
new Thread(consumer).start();
consumed task1
timed out, shutdown check

Пояснение: poll с таймаутом позволяет корректно завершать потребителя при отсутствии задач.

3) Комбинация poll и Optional для компактной обработки

Пример java
Queue<String> q = new LinkedList<>();
q.add("x");
java.util.Optional.ofNullable(q.poll())
    .ifPresent(v -> System.out.println("got: " + v));
got: x

Пояснение: уменьшает количество явных проверок на null.

4) Выполнение фоновых задач с адаптацией интервала по загрузке

Пример java
Queue<Runnable> q = new ConcurrentLinkedQueue<>();
q.add(() -> System.out.println("job1"));
q.add(() -> System.out.println("job2"));
int idleCount = 0;
while (idleCount < 3) {
    Runnable job = q.poll();
    if (job == null) {
        idleCount++;
        try { Thread.sleep(100 * idleCount); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    } else {
        idleCount = 0;
        job.run();
    }
}
System.out.println("stopped after idle");
job1
job2
stopped after idle

Пояснение: адаптивная задержка при простое уменьшает потребление ресурсов, избегая блокирования.

5) Использование pollFirst/pollLast для Deque

Пример java
Deque<String> d = new ArrayDeque<>();
d.add("a");
d.add("b");
System.out.println(d.pollFirst());
System.out.println(d.pollLast());
System.out.println(d.pollFirst());
a
b
null

Пояснение: полезно при реализации deque-очередей с приоритетной обработкой концов.

6) Использование poll в неблокирующем цикле для graceful shutdown

Пример java
AtomicBoolean running = new AtomicBoolean(true);
Queue<String> q = new ConcurrentLinkedQueue<>();
// другой поток добавит null-метку и вызовет running.set(false) для shutdown
while (running.get() || !q.isEmpty()) {
    String s = q.poll();
    if (s != null) {
        System.out.println(s);
    }
}
System.out.println("exited loop");
-- вывод зависит от добавляемых данных
exited loop

Пояснение: сочетание флага и очистки очереди обеспечивает корректное завершение без блокирования.

джава Queue.poll function comments

En
Queue.poll Retrieves and removes the head of this queue, or returns null if this queue is empty