Deque は「両端から出し入れできるキュー」
Deque(デック)は “Double Ended Queue” の略で、
「先頭」と「末尾」の両方から要素を出し入れできるコレクションです。
普通のキューは「末尾に入れて先頭から出す(FIFO)」だけですが、
Deque は「先頭に入れる」「末尾から出す」もできます。
つまり、
キュー(FIFO)としても、スタック(LIFO)としても使える、
ちょっと器用な箱だと思ってください。
業務では、
履歴、バッファ、固定長キュー、簡易スタックなど、
「順番を持ったデータ」を扱う場面でよく使います。
Deque の代表実装:ArrayDeque をまず覚える
なぜ LinkedList ではなく ArrayDeque なのか
Deque の代表的な実装は ArrayDeque です。
import java.util.ArrayDeque;
import java.util.Deque;
Deque<String> deque = new ArrayDeque<>();
JavaLinkedList も Deque を実装していますが、
業務で「単純にキュー/スタックとして使う」なら、
基本的には ArrayDeque を選んでおけば大丈夫です。
理由はシンプルで、
メモリ効率と速度のバランスが良く、
多くのケースで LinkedList より速いことが多いからです。
「Deque を使いたいな」と思ったら、
まず ArrayDeque を思い出す、くらいでちょうどいいです。
キューとしての Deque:末尾に入れて先頭から出す
offerLast と pollFirst の組み合わせ
キュー(FIFO)として使うときは、
「末尾に追加」「先頭から取り出し」という形になります。
import java.util.ArrayDeque;
import java.util.Deque;
public class QueueExample {
public static void main(String[] args) {
Deque<String> queue = new ArrayDeque<>();
queue.offerLast("A"); // 末尾に入れる
queue.offerLast("B");
queue.offerLast("C");
System.out.println(queue.pollFirst()); // A
System.out.println(queue.pollFirst()); // B
System.out.println(queue.pollFirst()); // C
System.out.println(queue.pollFirst()); // null(空)
}
}
Javaここでの重要ポイントは二つです。
一つ目は、「offerLast / pollFirst を使うと、“キューとしての意図”がコードに出る」ことです。add / remove でも動きますが、offer / poll の方が「キューっぽさ」が伝わります。
二つ目は、「pollFirst は空のときに null を返す」という点です。removeFirst だと空のときに例外になるので、
「空かもしれない」前提なら poll 系を使う方が安全です。
スタックとしての Deque:末尾に積んで末尾から取り出す
push / pop ではなく、addLast / pollLast で書く
Deque はスタック(LIFO)としても使えます。
import java.util.ArrayDeque;
import java.util.Deque;
public class StackExample {
public static void main(String[] args) {
Deque<String> stack = new ArrayDeque<>();
stack.addLast("A"); // 末尾に積む
stack.addLast("B");
stack.addLast("C");
System.out.println(stack.pollLast()); // C
System.out.println(stack.pollLast()); // B
System.out.println(stack.pollLast()); // A
}
}
Javapush / pop というメソッドもありますが、
業務コードでは addLast / pollLast の方が
「Deque としての一貫した書き方」になって読みやすいことが多いです。
ここでの重要ポイントは、
「キューとスタックの両方を、同じ Deque で表現できる」ことです。
どちらとして使っているかが、メソッド名から読み取れるように書くのが大事です。
両端を使う Deque:履歴やバッファに向いている
先頭側・末尾側を意識して設計する
Deque の強みは、「先頭」と「末尾」を自由に使えることです。
例えば、「最新が末尾、古いものが先頭」というルールで履歴を持つとします。
Deque<String> history = new ArrayDeque<>();
// 新しい履歴を追加
history.offerLast("操作1");
history.offerLast("操作2");
history.offerLast("操作3");
// 一番古い履歴を捨てる
history.pollFirst();
// 一番新しい履歴を参照
String latest = history.peekLast();
Javaここでのポイントは、
「どちら側を“新しい”とみなすか」を決めておくことです。
新しいものを末尾に積むのか、先頭に積むのか。
古いものをどちら側から捨てるのか。
このルールをチームで共有しておくと、
Deque を使ったコードが読みやすくなります。
Deque を使った簡単ユーティリティ:固定長履歴
「最後の N 件だけ覚えておく」箱
Deque は、固定長の履歴やバッファと相性が良いです。
例えば、「最後の 3 件だけ覚えておく履歴」を作ってみます。
import java.util.ArrayDeque;
import java.util.Deque;
public class FixedHistory<T> {
private final int maxSize;
private final Deque<T> deque = new ArrayDeque<>();
public FixedHistory(int maxSize) {
this.maxSize = maxSize;
}
public void add(T value) {
if (deque.size() == maxSize) {
deque.pollFirst(); // 一番古いものを捨てる
}
deque.offerLast(value); // 新しいものを末尾に追加
}
public Deque<T> snapshot() {
return new ArrayDeque<>(deque);
}
}
Java使い方のイメージです。
FixedHistory<String> history = new FixedHistory<>(3);
history.add("A");
history.add("B");
history.add("C");
history.add("D"); // この時点で [B, C, D] だけが残る
Javaここでの重要ポイントは、
「Deque の両端操作だけで、“最後の N 件だけ残す”というルールを簡潔に表現できる」ことです。
Deque を使うときに気をつけたいこと
null を入れない、スレッドセーフではない、を意識する
Deque(特に ArrayDeque)には、いくつか注意点があります。
一つ目は、「null を要素として入れない」ことです。ArrayDeque は null を特別な値として扱うため、null を入れると挙動がややこしくなります。
二つ目は、「ArrayDeque はスレッドセーフではない」ことです。
複数スレッドから同時に触るなら、外側で同期を取るか、ConcurrentLinkedDeque のようなスレッドセーフ実装を検討します。
三つ目は、「どのメソッドを使うかで“意図”を伝える」ことです。add / remove でも動きますが、
キューとして使うなら offerLast / pollFirst、
スタックとして使うなら addLast / pollLast のように、
役割に合ったメソッド名を選ぶと、後から読む人が理解しやすくなります。
まとめ:Deque操作で身につけてほしい感覚
Deque は、
単なる「ちょっと便利なキュー」ではなく、
「両端を自由に使える“順序付きの箱”」です。
キューとして使うときは「末尾に入れて先頭から出す」。
スタックとして使うときは「末尾に積んで末尾から出す」。
履歴や固定長バッファとして使うときは、「どちら側を新しい/古いとみなすか」を決めて両端を使い分ける。
あなたのコードのどこかに、List に対して「先頭に add」「先頭から remove」を繰り返している箇所があれば、
そこを一度「Deque に置き換えられないか?」という目で眺めてみてください。
その小さな置き換えが、
「データの順序と出し入れの方向まで意識してコレクションを選べるエンジニア」への、
いい一歩になります。
