¿Por qué debería usar Deque sobre Stack?


157

Necesito una Stackestructura de datos para mi caso de uso. Debería poder insertar elementos en la estructura de datos y solo quiero recuperar el último elemento de la Pila. El JavaDoc para Stack dice:

La interfaz Deque y sus implementaciones proporcionan un conjunto más completo y coherente de operaciones de pila LIFO, que deben utilizarse con preferencia a esta clase. Por ejemplo:

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

Definitivamente no quiero un comportamiento sincronizado aquí, ya que utilizaré esta estructura de datos local para un método. Aparte de esto, ¿por qué debería preferir Dequepor Stackaquí?

PD: El javadoc de Deque dice:

Los Deques también se pueden usar como pilas LIFO (último en entrar, primero en salir). Esta interfaz debe usarse con preferencia a la clase de pila heredada.


1
Proporciona más métodos integrados, o más bien "un conjunto más completo y coherente" de ellos, lo que reducirá la cantidad de código que debe escribir si los aprovecha.

Respuestas:


190

Por un lado, es más sensible en términos de herencia. El hecho de que se Stackextiende Vectores realmente extraño, en mi opinión. Al principio de Java, la herencia se usó en exceso en la OMI, Propertiessiendo otro ejemplo.

Para mí, la palabra crucial en los documentos que citó es consistente . Dequeexpone un conjunto de operaciones que se trata de poder recuperar / agregar / eliminar elementos desde el inicio o el final de una colección, iterar, etc., y eso es todo. Deliberadamente, no hay forma de acceder a un elemento por posición, lo que Stackexpone porque es una subclase de Vector.

Ah, y tampoco Stacktiene interfaz, por lo que si sabes que necesitas Stackoperaciones, terminas comprometiéndote con una clase concreta específica, que generalmente no es una buena idea.

También como se señala en los comentarios, Stacky Dequetienen órdenes de iteración 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

que también se explica en los JavaDocs para Deque.iterator () :

Devuelve un iterador sobre los elementos de esta deque en la secuencia adecuada. Los elementos serán devueltos en orden de primero (cabeza) a último (cola).


10
el javadoc de ArrayDeque dice "Es probable que esta clase sea más rápida que Stack cuando se usa como stack, y más rápida que LinkedList cuando se usa como cola". ¿Cómo especifico si tengo la intención de usar esto como una pila o como una cola?
Geek

23
@ Geek: No lo haces. El punto es que si desea un comportamiento en cola, puede usarlo LinkedList, pero ArrayDequeue(a menudo) será más rápido. Si desea un comportamiento de pila, puede usarlo Stackpero ArrayDeque(a menudo) será más rápido.
Jon Skeet

77
¿No es menos sensato en términos de abstracción? Quiero decir, ninguna de las soluciones es realmente buena en términos de abstracción, porque Stacktiene el problema de exposición de representantes, pero si quiero una estructura de datos de la pila, me gustaría poder llamar a métodos como push, pop y peek, y no a las cosas que tienen que hacer con el otro extremo de la pila.
PeteyPabPro

44
@JonSkeet también el iterador de Stack está mal, porque itera de abajo hacia arriba en lugar de arriba hacia abajo. stackoverflow.com/questions/16992758/…
Pavel

1
@PeteyPabPro: Tienes razón. El uso de Dequeue como stack todavía permite el uso sin LIFO como los métodos vectoriales heredados de Stack. Puede encontrar una explicación del problema y una solución (con encapsulación) aquí: baddotrobot.com/blog/2013/01/10/stack-vs-deque
rics

4

Aquí está mi interpretación de la inconsistencia mencionada en la descripción de la clase Stack.

Si observa las implementaciones de propósito general aquí , verá que hay un enfoque coherente para la implementación del conjunto, el mapa y la lista.

  • Para set y map tenemos 2 implementaciones estándar con mapas hash y árboles. El primero se usa más y el segundo se usa cuando necesitamos una estructura ordenada (y también implementa su propia interfaz: SortedSet u SortedMap).

  • Podemos usar el estilo preferido de declaración como Set<String> set = new HashSet<String>();ver razones aquí .

Pero la clase Stack: 1) no tiene su propia interfaz; 2) es una subclase de la clase Vector, que se basa en una matriz redimensionable; Entonces, ¿dónde está la implementación de la lista vinculada de la pila?

En la interfaz Deque no tenemos tales problemas, incluidas dos implementaciones (matriz redimensionable - ArrayDeque; lista enlazada - LinkedList).


2

Una razón más para usar Dequeue sobre Stack es que Dequeue tiene la capacidad de usar streams convert to list y mantener el concepto LIFO aplicado mientras que 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

Aquí hay algunas razones por las que Deque es mejor que Stack:

Diseño orientado a objetos: herencia, abstracción, clases e interfaces: Stack es una clase, Deque es una interfaz. Solo se puede extender una clase, mientras que una sola clase en Java puede implementar cualquier cantidad de interfaces (herencia de tipo múltiple). El uso de la interfaz Deque elimina la dependencia de la clase Stack concreta y sus antepasados ​​y le brinda más flexibilidad, por ejemplo, la libertad de extender una clase diferente o intercambiar diferentes implementaciones de Deque (como LinkedList, ArrayDeque).

Inconsistencia: Stack extiende la clase Vector, que le permite acceder al elemento por índice. Esto es inconsistente con lo que realmente debería hacer una Pila, por lo que se prefiere la interfaz Deque (no permite tales operaciones): sus operaciones permitidas son consistentes con lo que una estructura de datos FIFO o LIFO debería permitir.

Rendimiento: la clase de Vector que Stack extiende es básicamente la versión "segura para subprocesos" de una ArrayList. Las sincronizaciones pueden causar un impacto significativo en el rendimiento de su aplicación. Además, extender otras clases con funcionalidad innecesaria (como se menciona en el n. ° 2) hincha sus objetos, lo que puede costar una gran cantidad de memoria adicional y sobrecarga de rendimiento.


-1

Para mí, este punto específico faltaba: Stack es Threadsafe ya que se deriva de Vector, mientras que las implementaciones más antiguas no lo son, y por lo tanto más rápido si solo lo usa en un solo hilo.


grep "Aparte de esto"
Pacerier

-1

El rendimiento podría ser una razón. Un algoritmo que utilicé pasó de 7.6 minutos a 1.5 minutos simplemente reemplazando Stack con Deque.


grep "Aparte de esto"
Pacerier

-6

Deque se utiliza en una situación en la que desea recuperar elementos de la cabeza y la cola. Si quieres una pila simple, no hay necesidad de ir a una deque.


1
por favor vea el último párrafo de la pregunta. El javadoc dice que deberían usarse Deques y quería saber por qué.
Geek

2
Se prefiere Deque porque no puede acceder a elementos en el medio. Tiene doble terminación, por lo que puede ser FILO para FIFO.
Thufir

Creo que lo que pretenden decir es que la clase admite las operaciones de Stack como métodos explícitos. Ver su documentación y métodos. Tiene soporte explícito para implementar una pila.
Abhishek Nandgaonkar
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.