Perché dovrei usare Deque su Stack?


157

Ho bisogno di una Stackstruttura di dati per il mio caso d'uso. Dovrei essere in grado di inserire gli elementi nella struttura dei dati e desidero solo recuperare l'ultimo elemento dallo Stack. Il JavaDoc per Stack dice:

Un set più completo e coerente di operazioni stack LIFO è fornito dall'interfaccia Deque e dalle sue implementazioni, che dovrebbero essere utilizzate in preferenza a questa classe. Per esempio:

Deque<Integer> stack = new ArrayDeque<>();

Sicuramente non voglio un comportamento sincronizzato qui poiché userò questa struttura di dati locale per un metodo. Oltre a questo il motivo per cui dovrei preferire Dequesopra Stackqui?

PS: Il javadoc di Deque dice:

Deques può anche essere usato come stack LIFO (Last-In-First-Out). Questa interfaccia dovrebbe essere utilizzata in preferenza alla classe Stack legacy.


1
Fornisce metodi più integrati, o piuttosto "un insieme più completo e coerente", che ridurrà la quantità di codice che devi scrivere se ne approfitti?

Risposte:


190

Per prima cosa, è più sensato in termini di eredità. Il fatto che si Stackestenda Vectorè davvero strano, a mio avviso. All'inizio di Java, l'ereditarietà era un uso eccessivo dell'IMO - Propertiesun altro esempio.

Per me, la parola cruciale nei documenti che hai citato è coerente . Dequeespone un insieme di operazioni che riguardano la possibilità di recuperare / aggiungere / rimuovere elementi dall'inizio o alla fine di una raccolta, iterare ecc. e basta. Non c'è deliberatamente alcun modo per accedere a un elemento per posizione, il che Stackespone perché è una sottoclasse di Vector.

Oh, e Stackha anche un'interfaccia, quindi se sai di aver bisogno di Stackoperazioni finisci per impegnarti in una specifica classe concreta, che di solito non è una buona idea.

Inoltre, come sottolineato nei commenti, Stacke Dequehanno ordini di iterazione inversa:

Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println(new ArrayList<>(stack)); // prints 1, 2, 3


Deque<Integer> deque = new ArrayDeque<>();
deque.push(1);
deque.push(2);
deque.push(3);
System.out.println(new ArrayList<>(deque)); // prints 3, 2, 1

che è anche spiegato nei JavaDocs per Deque.iterator () :

Restituisce un iteratore sugli elementi di questo deque nella sequenza corretta. Gli elementi verranno restituiti in ordine dal primo (testa) all'ultimo (coda).


10
il javadoc di ArrayDeque dice "È probabile che questa classe sia più veloce di Stack quando usata come una pila e più veloce di LinkedList se usata come una coda." ..Come posso specificare se intendo usarlo come stack o come coda?
Geek,

23
@Geek: non lo fai. Il punto è che se si desidera il comportamento in coda, è possibile utilizzare LinkedList, ma ArrayDequeuesarà (spesso) più veloce. Se si desidera il comportamento dello stack, è possibile utilizzare Stackma ArrayDeque(spesso) sarà più veloce.
Jon Skeet,

7
Tuttavia, non è meno sensato in termini di astrazione? Voglio dire, nessuna soluzione è davvero buona in termini di astrazione, perché Stackha il problema dell'esposizione rep, ma se voglio una struttura di dati dello stack vorrei essere in grado di chiamare metodi come push, pop e peek, e non cose che devono fare con l'altra estremità della pila.
PeteyPabPro,

4
@JonSkeet anche l'iteratore di Stack è sbagliato, perché scorre dal basso verso l'alto invece che dall'alto verso il basso. stackoverflow.com/questions/16992758/…
Pavel,

1
@PeteyPabPro: hai ragione. L'uso di Dequeue come stack consente comunque l'utilizzo non LIFO come metodi Vector ereditati di Stack. Una spiegazione del problema e una soluzione (con incapsulamento) può essere trovato qui: baddotrobot.com/blog/2013/01/10/stack-vs-deque
RICS

4

Ecco la mia interpretazione di incoerenza menzionata nella descrizione della classe Stack.

Se guardi le implementazioni per scopi generici qui , vedrai che esiste un approccio coerente all'implementazione di set, mappa ed elenco.

  • Per set e mappe abbiamo 2 implementazioni standard con mappe hash e alberi. Il primo è il più usato e il secondo è usato quando abbiamo bisogno di una struttura ordinata (e implementa anche la propria interfaccia - SortedSet o SortedMap).

  • Possiamo usare lo stile preferito di dichiarazione come Set<String> set = new HashSet<String>();vedere qui le ragioni .

Ma classe Stack: 1) non ha una propria interfaccia; 2) è una sottoclasse della classe Vector - che si basa su un array ridimensionabile; quindi dov'è l'implementazione dell'elenco collegato dello stack?

Nell'interfaccia Deque non abbiamo tali problemi tra cui due implementazioni (array ridimensionabile - ArrayDeque; elenco collegato - LinkedList).


2

Un motivo in più per usare Dequeue su Stack è che Dequeue ha la capacità di usare i flussi convertiti in elenco mantenendo il concetto LIFO applicato mentre Stack no.

Stack<Integer> stack = new Stack<>();
Deque<Integer> deque = new ArrayDeque<>();

stack.push(1);//1 is the top
deque.push(1)//1 is the top
stack.push(2);//2 is the top
deque.push(2);//2 is the top

List<Integer> list1 = stack.stream().collect(Collectors.toList());//[1,2]

List<Integer> list2 = deque.stream().collect(Collectors.toList());//[2,1]

2

Ecco alcuni motivi per cui Deque è meglio di Stack:

Progettazione orientata agli oggetti - Ereditarietà, astrazione, classi e interfacce: Stack è una classe, Deque è un'interfaccia. È possibile estendere solo una classe, mentre qualsiasi numero di interfacce può essere implementato da una singola classe in Java (eredità multipla di tipo). L'uso dell'interfaccia Deque rimuove la dipendenza dalla classe Stack concreta e dai suoi antenati e ti dà maggiore flessibilità, ad esempio la libertà di estendere una classe diversa o scambiare diverse implementazioni di Deque (come LinkedList, ArrayDeque).

Incoerenza: Stack estende la classe Vector, che consente di accedere all'elemento per indice. Ciò non è coerente con ciò che uno Stack dovrebbe effettivamente fare, motivo per cui si preferisce l'interfaccia Deque (non consente tali operazioni) - le sue operazioni consentite sono coerenti con ciò che una struttura di dati FIFO o LIFO dovrebbe consentire.

Prestazioni: la classe Vector estesa da Stack è sostanzialmente la versione "thread-safe" di una ArrayList. Le sincronizzazioni possono potenzialmente causare un impatto significativo sulle prestazioni dell'applicazione. Inoltre, l'estensione di altre classi con funzionalità non necessarie (come menzionato nel n. 2) gonfia i tuoi oggetti, potenzialmente costando molta memoria aggiuntiva e sovraccarico di prestazioni.


-1

Per me questo punto specifico mancava: Stack è Threadsafe in quanto derivato da Vector, mentre le implementazioni più deque non lo sono, e quindi più veloce se lo si utilizza solo in un singolo thread.


grep "A parte questo"
Pacerier

-1

Le prestazioni potrebbero essere un motivo. Un algoritmo che ho usato è passato da 7,6 minuti a 1,5 minuti sostituendo semplicemente Stack con Deque.


grep "A parte questo"
Pacerier

-6

Deque viene utilizzato è la situazione in cui si desidera recuperare elementi sia dalla testa che dalla coda. Se vuoi uno stack semplice, non c'è bisogno di fare un deque.


1
si prega di consultare l'ultimo paragrafo della domanda. Il javadoc dice che Deques dovrebbe essere usato e volevo sapere perché?
Geek,

2
Deque è preferito perché non è possibile accedere agli elementi nel mezzo. Ha un doppio attacco, quindi può essere FILO per FIFO.
Thufir,

Penso che ciò che intendono dire è che la classe supporta le operazioni Stack come metodi espliciti. Vedi la sua documentazione e metodi. Ha il supporto esplicito per l'implementazione di uno Stack.
Abhishek Nandgaonkar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.