W3docs

Interfaccia Java Deque

Operazioni su coda a doppia estremità in Java con l'interfaccia Deque — addFirst, addLast, pollFirst, pollLast.

Deque<E> — pronunciato "deck," abbreviazione di double-ended queue — è Queue<E> con una seconda estremità. Mentre una Queue consente solo di rimuovere dalla testa, una Deque permette di inserire, rimuovere ed esaminare entrambe la testa e la coda. Questa singola capacità aggiuntiva è sufficiente per rendere Deque il contratto alla base di due astrazioni completamente diverse: è una coda, è uno stack, e il JDK la raccomanda come valore predefinito per entrambi.

Due estremità × tre operazioni × due stili di errore

L'interfaccia sembra intimidatoria a prima vista perché ogni operazione appare in dodici forme — testa vs coda, inserimento vs rimozione vs esame, lancia eccezione vs restituisce valore speciale. Ma è solo la griglia 3×2 di Queue estesa su due estremità:

OperazionePrima (testa) — lanciaPrima (testa) — valore specialeUltima (coda) — lanciaUltima (coda) — valore speciale
InserimentoaddFirst(e)offerFirst(e)addLast(e)offerLast(e)
RimozioneremoveFirst()pollFirst()removeLast()pollLast()
EsamegetFirst()peekFirst()getLast()peekLast()

La colonna "lancia" è quella da usare quando una deque vuota/piena in quel punto sarebbe un bug. La colonna "valore speciale" è quella da usare quando lo stato vuoto è atteso e si vuole verificarlo in una condizione di ciclo.

Il mapping della coda

Deque estende Queue, quindi una Deque è una Queue. I metodi ereditati sono alias per le varianti dalla testa alla coda:

Metodo QueueEquivalente Deque
add(e) / offer(e)addLast(e) / offerLast(e)
remove() / poll()removeFirst() / pollFirst()
element() / peek()getFirst() / peekFirst()

Inserimento dalla coda, rimozione dalla testa — questa è la disciplina FIFO che Queue già fornisce, solo espressa con i nomi espliciti dell'estremità.

Il mapping dello stack

Deque definisce anche tre metodi "stack" che agiscono tutti sulla testa:

Metodo StackEquivalente Deque
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

Push in entrata, pop in uscita — disciplina LIFO, stessa testa, estremità opposta rispetto al mapping di inserimento/rimozione della coda. Ecco perché il capitolo sullo Stack ti rimanda qui: il modo moderno di scrivere uno stack in Java è Deque<E> stack = new ArrayDeque<>(); e chiamare push / pop / peek.

La regola: null non è consentito

Come Queue, il contratto Deque riserva null come sentinel "vuoto" per pollFirst, pollLast, peekFirst, peekLast. Quindi ogni Deque di uso generale nel JDK rifiuta gli elementi null con NullPointerException all'inserimento. L'unica eccezione è LinkedList, che permette null per ragioni storiche — ed eredita tutta l'ambiguità che ne deriva. Non farlo.

Iterazione e iterazione inversa

Una Deque ha per convenzione due iteratori:

  • iterator() percorre dalla testa alla coda — l'ordine in cui si chiamerebbe pollFirst ripetutamente.
  • descendingIterator() percorre dalla coda alla testa — l'ordine in cui si chiamerebbe pollLast ripetutamente.

Entrambi sono tipicamente fail-fast: mutare la deque dall'esterno dell'iteratore durante l'iterazione lancia ConcurrentModificationException. Usa Iterator.remove() o removeIf se devi mutare durante una scansione.

Scegliere un'implementazione

Nel codice single-threaded ci sono essenzialmente due scelte:

  • ArrayDeque — quella predefinita. Array circolare, O(1) su entrambe le estremità, nessuna allocazione di nodi per elemento, cache-friendly. Rifiuta null. Implementa Deque e Queue.
  • LinkedList — nodi doppiamente collegati. Implementa anche List, quindi ogni elemento ha un nodo con puntatori prev/next. Più lenta su entrambe le estremità rispetto ad ArrayDeque e usa più memoria. Sceglila solo se hai effettivamente bisogno della semantica Deque e List sullo stesso oggetto.

Per il codice concorrente (trattato più avanti nella parte sulla concorrenza del libro):

  • ConcurrentLinkedDeque — lock-free, illimitata.
  • LinkedBlockingDeque — opzionalmente limitata, bloccante — la versione da usare per costruire una coda work-stealing o un producer/consumer con back-pressure su entrambe le estremità.

Un esempio pratico: coda, stack e verifica di palindromo in un'unica deque

Il programma seguente usa un ArrayDeque come coda FIFO, un altro come stack LIFO, e un terzo per verificare se una frase è un palindromo alimentando entrambe le estremità e confrontandole. Il punto è che la stessa interfaccia, persino la stessa classe, svolge tutti e tre i ruoli.

java— editable, runs on the server

Ciò che questa esecuzione mostra:

  • La stessa classe svolge sia il ruolo di coda che di stack senza rinominare un singolo metodo, se non per quale estremità si indirizza.
  • I metodi "restituisce valore speciale" producono silenziosamente null per la deque vuota; i metodi "lancia eccezione" sollevano NoSuchElementException. Scegli in base al fatto che lo stato vuoto sia atteso o un bug.
  • La verifica del palindromo usa entrambe le estremità nello stesso ciclopollFirst e pollLast insieme. Questo è il pattern di accesso che solo una Deque fornisce in modo efficiente.

Cosa c'è dopo

Il capitolo su Deque chiude il lato delle code del framework. L'altra grande astrazione in Collection è quella che rifiuta i duplicati: un Set è una Collection con il contratto di unicità integrato. Lo affrontiamo nella prossima sezione.

Pratica

Pratica
Vuoi uno stack LIFO nel codice Java moderno. Quale riga corrisponde alla raccomandazione del JDK?
Vuoi uno stack LIFO nel codice Java moderno. Quale riga corrisponde alla raccomandazione del JDK?
Was this page helpful?