Почему я должен использовать Deque over Stack?

157

Мне нужна Stackструктура данных для моего варианта использования. Я должен иметь возможность помещать элементы в структуру данных, и я хочу получить только последний элемент из стека. JavaDoc для стека говорит:

Более полный и согласованный набор операций стека LIFO обеспечивается интерфейсом Deque и его реализациями, которые следует использовать в предпочтении этому классу. Например:

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

Я определенно не хочу синхронизированное поведение здесь, поскольку я буду использовать эту локальную для метода структуру данных. Кроме того , почему я предпочел Dequeболее Stackздесь?

PS: Javadoc от Deque говорит:

Deques также может использоваться в качестве стеков LIFO (Last-In-First-Out). Этот интерфейс следует использовать предпочтительнее унаследованного класса Stack.

Фанат
источник
1
Он предоставляет больше встроенных методов или, точнее, «более полный и согласованный набор» их, что уменьшит объем кода, который вам придется писать, если вы воспользуетесь ими?

Ответы:

190

С одной стороны, это более разумно с точки зрения наследования. То, что Stackраспространяется Vector, действительно странно, на мой взгляд. На ранних этапах Java наследование было чрезмерным использованием IMO, Propertiesчто является еще одним примером.

Для меня ключевое слово в документах, которые вы цитировали, является последовательным . Dequeпредоставляет набор операций, которые предназначены для возможности извлечения / добавления / удаления элементов из начала или конца коллекции, итерации и т. д. - и все. Умышленно нет никакого способа получить доступ к элементу по позиции, который Stackвыставляется, потому что это подкласс Vector.

Да, и также Stackне имеет интерфейса, поэтому, если вы знаете, что вам нужны Stackоперации, вы в конечном итоге фиксируете определенный конкретный класс, что обычно не является хорошей идеей.

Также, как указано в комментариях, Stackи Dequeимеют обратный порядок итераций:

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

что также объясняется в JavaDocs для Deque.iterator () :

Возвращает итератор для элементов в этой deque в правильной последовательности. Элементы будут возвращены в порядке от первого (голова) до последнего (хвост).

Джон Скит
источник
10
Javadoc ArrayDeque говорит: «Этот класс, вероятно, будет быстрее, чем Stack, когда используется в качестве стека, и быстрее, чем LinkedList, когда используется в качестве очереди». ..Как мне указать, собираюсь ли я использовать это как стек или как очередь?
Компьютерщик
23
@ Geek: Вы не делаете. Дело в том, что если вам нужно поведение в очереди, вы можете использовать LinkedList, но ArrayDequeue(часто) будет быстрее. Если вы хотите поведение стека, вы можете использовать, Stackно ArrayDeque(часто) будет быстрее.
Джон Скит
7
Разве это не менее разумно с точки зрения абстракции? Я имею в виду, что ни одно из решений не является действительно хорошим с точки зрения абстракции, потому что Stackимеет проблему с представлением повторений, но если я хочу иметь структуру данных стека, я хотел бы иметь возможность вызывать такие методы, как push, pop и peek, а не вещи, требующие делать с другим концом стека.
PeteyPabPro
4
@JonSkeet итератор стека неправильный, потому что он перебирает снизу вверх, а не сверху вниз. stackoverflow.com/questions/16992758/…
Павел
1
@PeteyPabPro: Вы правы. Использование Dequeue в качестве стека по-прежнему допускает использование без LIFO в качестве унаследованных методов Vector стека. Объяснение проблемы и ее решение (с инкапсуляцией) можно найти здесь: baddotrobot.com/blog/2013/01/10/stack-vs-deque
rics
4

Вот моя интерпретация несоответствия, упомянутая в описании класса Stack.

Если вы посмотрите на Реализации общего назначения здесь - вы увидите, что существует последовательный подход к реализации множества, карты и списка.

  • Для множества и карты у нас есть 2 стандартные реализации с хэш-картами и деревьями. Первый наиболее часто используется, а второй используется, когда нам нужна упорядоченная структура (и он также реализует свой собственный интерфейс - SortedSet или SortedMap).

  • Мы можем использовать предпочтительный стиль объявления, как Set<String> set = new HashSet<String>();показано здесь .

Но класс Stack: 1) не имеет собственного интерфейса; 2) является подклассом класса Vector, который основан на массиве изменяемого размера; Так где же реализация связанного списка стека?

В интерфейсе Deque у нас нет таких проблем, включая две реализации (изменяемый размер массива - ArrayDeque; связанный список - LinkedList).

irudyak
источник
2

Еще одна причина использовать Dequeue поверх стека состоит в том, что Dequeue имеет возможность использовать преобразование потоков в список с сохранением концепции LIFO, в то время как Stack этого не делает.

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

Вот несколько причин, почему Deque лучше, чем стек:

Объектно-ориентированный дизайн - наследование, абстракция, классы и интерфейсы: стек - это класс, Deque - это интерфейс. Только один класс может быть расширен, тогда как любое количество интерфейсов может быть реализовано одним классом в Java (множественное наследование типа). Использование интерфейса Deque удаляет зависимость от конкретного класса Stack и его предков и дает вам больше гибкости, например, свободу расширять другой класс или менять различные реализации Deque (например, LinkedList, ArrayDeque).

Несоответствие: стек расширяет класс Vector, что позволяет получить доступ к элементу по индексу. Это несовместимо с тем, что на самом деле должен делать стек, именно поэтому предпочтительным является интерфейс Deque (он не разрешает такие операции) - его разрешенные операции соответствуют тому, что должна разрешать структура данных FIFO или LIFO.

Производительность: класс Vector, который расширяет Stack, по сути является «потокобезопасной» версией ArrayList. Синхронизация может привести к значительному снижению производительности вашего приложения. Кроме того, расширение других классов ненужной функциональностью (как упомянуто в # 2) увеличивает объем ваших объектов, что может потребовать значительных дополнительных затрат памяти и производительности.

Арпит Бхаргава
источник
-1

Для меня этот конкретный момент упущен: стек является Threadsafe, поскольку он является производным от Vector, тогда как большинство реализаций deque нет, и, следовательно, быстрее, если вы используете его только в одном потоке.

И я
источник
grep "Помимо этого"
Pacerier
-1

Производительность может быть причиной. Алгоритм, который я использовал, сократился с 7,6 до 1,5 минут, просто заменив Stack на Deque.

Эдди
источник
grep "Помимо этого"
Pacerier
-6

Deque используется в ситуации, когда вы хотите получить элементы как из головы, так и из хвоста. Если вы хотите простой стек, вам не нужно идти на дек.

край
источник
1
пожалуйста, смотрите последний праграф в вопросе. Javadoc говорит, что Deques должны быть использованы, и я хотел знать, почему?
Компьютерщик
2
Deque предпочтительнее, потому что вы не можете получить доступ к элементам в середине. Это двойной конец, поэтому может быть FILO для FIFO.
Thufir
Я думаю, что они намерены сказать, что класс поддерживает операции Stack как явные методы. Смотрите его документацию и методы. Он имеет явную поддержку для реализации стека.
Абхишек Нандгаонкар