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

Метод peek интерфейса Queue: свойства и примеры
Раздел: Коллекции (Collection Framework) - Queue/Deque
Queue.peek: E

Общее описание метода

Метод Queue.peek() из пакета java.util возвращает элемент в голове очереди, не удаляя его. Применяется для получения текущего «первого» элемента без изменения структуры очереди. Возвращаемый тип - обобщённый E, соответствующий параметру типа очереди.

Сигнатура: E peek(). Аргументов нет. Поведение:

  • Если очередь не пуста, возвращается элемент, находящийся в голове.
  • Если очередь пуста, возвращается null. Это отличает peek() от метода element(), который при пустой очереди выбрасывает NoSuchElementException.

Особенности в разных реализациях:

  • Для PriorityQueue «голова» - наименьший (или наибольший) элемент в соответствии с компаратором.
  • Для реализаций из пакета java.util.concurrent значение, возвращаемое peek(), может быть устаревшим в многопоточной среде, так как операция не синхронизирована сама по себе.

Ограничения и требования:

  • Некоторые реализации не допускают null в качестве элемента; попытка вставки null приводит к исключению (например, PriorityQueue и большинство concurrent-реализаций).
  • Сложность выполнения зависит от реализации: для большинства очередь-списков это O(1), для приоритетных - O(1) для чтения головы, но поддержание структуры при вставке/удалении иного характера.

Короткие примеры применения

Пример 1: LinkedList как очередь, чтение головы без удаления.

import java.util.*;

Queue q = new LinkedList<>();
q.add("a");
q.add("b");
String head = q.peek();
System.out.println(head);
System.out.println(q);
a
[a, b]

Пример 2: Поведение для пустой очереди.

Queue q = new ArrayDeque<>();
System.out.println(q.peek());
// сравнение с element()
try {
  System.out.println(q.element());
} catch (Exception e) {
  System.out.println(e.getClass().getSimpleName());
}
null
NoSuchElementException

Пример 3: PriorityQueue возвращает элемент по приоритету.

Queue pq = new PriorityQueue<>();
pq.add(20);
pq.add(5);
pq.add(15);
System.out.println(pq.peek());
System.out.println(pq);
5
[5, 20, 15]  // внутреннее представление может отличаться

Пример 4: ConcurrentLinkedQueue - неблокирующее чтение головы в многопоточной среде.

import java.util.concurrent.*;

Queue cq = new ConcurrentLinkedQueue<>();
cq.add("x");
System.out.println(cq.peek());
// другой поток может одновременно изменить очередь
x

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

Краткий обзор альтернатив и отличий:

  • element() - возвращает голову, но при пустой очереди выбрасывает NoSuchElementException. Подходит, когда пустая очередь считается ошибкой.
  • poll() - возвращает и удаляет голову; при пустой очереди возвращает null. Предпочтительнее для потребления элементов.
  • remove() - удаляет и возвращает голову, при пустой очереди выбрасывает NoSuchElementException. Аналогичен element() по семантике ошибок.
  • В интерфейсе Deque доступны peekFirst() и peekLast() для двусторонних очередей, когда требуется смотреть соответствующие концы.
  • Для блокирующих очередей (BlockingQueue) имеются методы take() (блокирует до появления элемента) и poll(timeout, unit) (ждёт ограниченное время).

Сопоставление с другими языками

Краткие аналоги и ключевые отличия в популярных языках с примерами.

JavaScript - массив или очередь из библиотек. Псевдоприкладной пример:

const q = ['a','b'];
const head = q[0];
console.log(head);
console.log(q);
a
['a','b']

В JS встроенной семантики очереди нет: доступ по индексу возвращает undefined для пустой коллекции вместо исключения.

Python: коллекция deque и класс queue.Queue.

from collections import deque
q = deque(['a','b'])
print(q[0])
# deque[0] при пустой очереди вызовет IndexError
a

Класс queue.Queue предназначен для потокобезопасного взаимодействия и не предоставляет прямого безопасного peek; обходные пути включают использование блокировок или внутренних очередей (не рекомендуется).

C#: System.Collections.Generic.Queue<T> имеет Peek(), который при пустой очереди бросает InvalidOperationException.

var q = new Queue();
q.Enqueue("a");
Console.WriteLine(q.Peek());
a

Go: стандарт - срезы или кольцевые буферы. «Peek» реализуется как обращение к элементу по индексу, при пустом срезе будет паника.

q := []string{"a","b"}
fmt.Println(q[0])
a

Kotlin: может использовать java.util.Queue и методы peek() а также удобные расширения: firstOrNull() для коллекций.

val q: java.util.Queue = ArrayDeque()
q.add("a")
println(q.peek())
a

PHP: класс SplQueue допускает методы bottom() и top() для доступа к концам; получение без удаления обычно делается через top() после перехода итератора.

$q = new SplQueue();
$q->enqueue('a');
echo $q->top();
a

Общее отличие: в Java peek() возвращает null при пустой очереди, тогда как в ряде языков доступ к первому элементу бросит исключение или вернёт спецзначение (undefined/None).

Типичные ошибки и ловушки

  • Ожидание исключения при пустой очереди. Пример ошибки: использование peek() вместо element() при логике, где пустота - ошибка.
Queue q = new ArrayDeque<>();
if (q.peek() == null) {
  // намерение: обработать как ошибка, но тут возвращается null
  System.out.println("пусто");
}
// если требовалось исключение, использовался бы element()
пусто
  • Попытка положить null в очередь и затем читать. Многие реализации (например, PriorityQueue и concurrent-очереди) запрещают null, что приводит к NullPointerException при добавлении.
Queue pq = new PriorityQueue<>();
try {
  pq.add(null);
} catch (Exception e) {
  System.out.println(e.getClass().getSimpleName());
}
NullPointerException
  • Неправильное использование в многопоточности: peek() не атомарен с операциями, зависящими от возвращённого значения. Пример гонки: проверка через peek(), затем условное удаление через poll() без синхронизации приводит к тому, что другой поток может удалить элемент между вызовами.
// возможная гонка
Queue q = new ConcurrentLinkedQueue<>();
String e = q.peek();
if (e != null && e.equals("task")) {
  // другой поток мог уже удалить этот элемент
  q.remove(e);
}
// поведение зависит от interleaving, возможны пропуски или дублированная обработка

Изменения в реализации за последние версии

Метод peek() как часть интерфейса Queue сохраняет стабильную семантику в современных релизах Java. В последних версиях не происходило изменения его базового контракта: отсутствие аргументов и возвращение null при пустой очереди. Развитие коснулось в основном новых реализаций очередей в пакете java.util.concurrent, которые обеспечивают дополнительные гарантии по многопоточности и эффективности, но не меняют поведение peek().

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

Сценарий 1: Планировщик задач с приоритетом по времени. peek() проверяет ближайшую задачу без её удаления; при наступлении времени выполняется poll().

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

class Task implements Comparable {
  long time;
  String name;
  Task(long t, String n){time=t;name=n;}
  public int compareTo(Task o){return Long.compare(this.time,o.time);}  
  public String toString(){return name+"@"+time;}
}

PriorityQueue pq = new PriorityQueue<>();
pq.add(new Task(System.currentTimeMillis()+2000, "t1"));
// имитация цикла планировщика:
Task next = pq.peek();
System.out.println("next="+next);
next=t1@1600000000000  // пример вывода, время будет реальным

Комментарий: использование peek() позволяет принимать решение о времени ожидания до следующей операции, не потребляя задачу преждевременно.

Сценарий 2: Безопасная обработка с Optional, чтобы явно работать с отсутствием значения.

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

Queue q = new ArrayDeque<>();
q.add("job");
Optional.ofNullable(q.peek()).ifPresent(s -> System.out.println("head: "+s));
head: job

Сценарий 3: Проверка состояния очереди в алгоритме с откатом. peek() даёт возможность посмотреть, что будет обработано, а при необходимости элемент можно удалить позже.

Пример java
Queue q = new LinkedList<>();
q.add(1); q.add(2);
Integer x = q.peek();
if (x != null && x > 0) {
  // выполнить проверку, и только после подтверждения удалить
  Integer removed = q.poll();
  System.out.println("removed="+removed);
}
System.out.println(q);
removed=1
[2]

Сценарий 4: Использование в неблокирующем кешировании: peek() проверяет, истёк ли верхний элемент по таймстампу, перед попыткой извлечения или обновления.

Пример java
// псевдокод: очередь пар (expiry, value)
Queue q = new LinkedList<>();
q.add(new long[]{System.currentTimeMillis()+1000, 42});
long[] top = q.peek();
if (top != null && top[0] <= System.currentTimeMillis()){
  q.poll(); // удалить просроченный
}
// поведение зависит от времени, пример: элемент может быть удалён после истечения срока

Важная ремарка: при использовании в многопоточном окружении требуется продуманная стратегия синхронизации, так как последовательность peek() и последующих операций не атомарна.

джава Queue.peek function comments

En
Queue.peek Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty