Stack dan Queue di Java: Konsep LIFO dan FIFO

Hai, teman-teman! Jam makan siang di Warteg Canggih. Budi memesan tempe orek dua porsi, nasi putih, lalu es teh. Kasir sudah mencatat semuanya, lalu Budi bilang, "Eh, es tehnya tidak jadi, Mas." Kasir versi 11 tidak punya tombol batal, jadi satu-satunya jalan adalah mengulang pesanan dari awal.

Di belakang Budi, tiga orang sudah berdiri mengantre, dan kasir tidak tahu siapa yang datang lebih dulu. Dua masalah ini kelihatannya beda, tapi jawabannya datang dari keluarga yang sama: stack dan queue di Java. Yang satu mengatur "yang terakhir masuk, keluar duluan", yang lain "yang pertama datang, dilayani duluan".

Sampul seri belajar Java Studi Kasus Warteg Canggih bagian 12: Stack dan Queue di Java, dengan ilustrasi struk kasir warteg
Seri Warteg Canggih bagian 12: kasir belajar membatalkan pesanan terakhir dan melayani antrian pembeli

TL;DR: Stack bekerja dengan prinsip LIFO (last in, first out): elemen yang terakhir dimasukkan adalah yang pertama diambil, cocok untuk membatalkan pesanan terakhir. Queue bekerja dengan prinsip FIFO (first in, first out): yang pertama masuk dilayani duluan, cocok untuk antrian pembeli. Di Java, keduanya paling praktis dibuat dengan ArrayDeque: push, pop, dan peek untuk stack, serta offer, poll, dan peek untuk queue. Kasir versi 12 memakai keduanya sekaligus.

Kartu istilah Stack atau tumpukan: Struktur data yang ditambah dan diambil dari atas, sehingga yang terakhir masuk keluar paling dulu
Seperti tumpukan piring di dapur warteg, pop() selalu mengambil yang paling atas

Yang Sudah Kita Pelajari

Artikel ini memakai bekal dari beberapa artikel sebelumnya:

Tujuan Artikel Ini

Di sini kamu akan belajar:

  • Melihat kenapa array biasa merepotkan untuk membatalkan pesanan.
  • Memahami stack dan prinsip LIFO, lalu membuatnya dengan ArrayDeque.
  • Memahami queue dan prinsip FIFO untuk antrian pembeli.
  • Membedakan stack dan queue, dan kapan memakai masing-masing.
  • Membangun kasir versi 12 yang bisa membatalkan pesanan terakhir dan melayani antrian.
  • Mengenali error yang sering muncul saat memakai stack dan queue.

Membatalkan Pesanan dengan Array Biasa

Sebelum kenalan dengan stack, coba selesaikan masalah Budi dengan alat yang sudah kita punya, yaitu array dari artikel Array. Pesanan disimpan berurutan dalam int[] berisi nomor index menu, ditambah satu variabel penghitung isi:

int[] pesanan = new int[4];
int jumlahPesanan = 0;

pesanan[jumlahPesanan] = 1;   // tempe orek
jumlahPesanan++;
pesanan[jumlahPesanan] = 0;   // nasi putih
jumlahPesanan++;
pesanan[jumlahPesanan] = 2;   // es teh
jumlahPesanan++;

Membatalkan pesanan terakhir ternyata gampang sekali:

jumlahPesanan--;   // es teh dianggap tidak ada

Isi pesanan[2] sebenarnya masih 2, tapi karena penghitungnya turun jadi 2, sekat itu dianggap kosong dan akan ditimpa oleh pesanan berikutnya.

Masalahnya muncul saat yang dibatalkan ada di tengah. Kalau Budi membatalkan nasi putih di posisi 1, semua pesanan di belakangnya harus digeser satu langkah ke kiri:

int posisiBatal = 1;
for (int i = posisiBatal; i < jumlahPesanan - 1; i++) {
    pesanan[i] = pesanan[i + 1];
}
jumlahPesanan--;

Lalu ada masalah ketiga: array ini cuma muat empat pesanan. Seperti dibahas di bagian "Ukurannya Tidak Bisa Ditambah" di artikel Array, panjang array tidak bisa berubah. Pesanan kelima harus menunggu kita membuat array baru yang lebih besar, lalu menyalin isinya:

if (jumlahPesanan == pesanan.length) {
    pesanan = Arrays.copyOf(pesanan, pesanan.length * 2);
}

Tiga operasi, tiga cara yang berbeda, dan semuanya harus kita jaga sendiri, termasuk penghitung yang lupa diturunkan. Padahal di kasir warteg, yang paling sering dibatalkan adalah yang barusan disebut. Ada struktur data yang memang dirancang khusus untuk pola seperti itu.

Kenalan Dulu: Apa Itu Stack di Java?

Stack adalah struktur data yang hanya bisa ditambah dan diambil dari satu ujung, yaitu bagian atas. Akibatnya, elemen yang terakhir masuk selalu jadi yang pertama keluar. Prinsip ini disebut LIFO (last in, first out). Operasi utamanya ada tiga: push untuk menaruh, pop untuk mengambil, dan peek untuk mengintip yang paling atas.

Coba lihat tumpukan piring bersih di dapur warteg. Piring yang baru dicuci ditaruh paling atas, dan saat ada pesanan, yang diambil juga piring paling atas. Piring paling bawah baru terpakai setelah semua piring di atasnya diambil. Tidak ada yang menarik piring dari tengah tumpukan.

Pesanan Budi persis seperti itu. Setiap pesanan baru ditaruh di atas tumpukan, dan tombol "batal" selalu mengambil yang paling atas.

Stack di Java dengan ArrayDeque

Untuk membuat stack, Java menyediakan ArrayDeque lewat interface Deque (dibaca "dek"). Deque (double-ended queue) adalah antrian yang bisa ditambah dan diambil dari kedua ujung, jadi bisa dipakai sebagai stack maupun queue. Saat dipakai sebagai stack, cukup pakai tiga method: push, pop, dan peek.

Deque<String> tumpukan = new ArrayDeque<>();
tumpukan.push("Tempe orek x2");
tumpukan.push("Nasi putih x1");
tumpukan.push("Es teh x1");

System.out.println(tumpukan);          // [Es teh x1, Nasi putih x1, Tempe orek x2]
System.out.println(tumpukan.peek());   // Es teh x1
System.out.println(tumpukan.pop());    // Es teh x1
System.out.println(tumpukan);          // [Nasi putih x1, Tempe orek x2]

Perhatikan cara mencetaknya: yang paling atas ditampilkan paling kiri. peek() cuma mengintip tanpa mengambil, sedangkan pop() mengambil dan membuangnya dari tumpukan. Tidak ada penghitung yang perlu dijaga, dan ukurannya bertambah sendiri sesuai kebutuhan.

Dua hal baru muncul di baris pertama:

  • import java.util.ArrayDeque; dan import java.util.Deque; wajib ditulis di atas file, sama seperti Scanner.
  • <String> menentukan isi tumpukan. Bentuk ini disebut generics, dan gunanya supaya compiler bisa menolak kalau kita memasukkan sesuatu yang bukan String. Penjelasan lengkapnya ada di tutorial Generics dari Oracle. <> di sebelah kanan boleh dikosongkan, karena Java bisa menebaknya dari sebelah kiri.

Isi generics harus berupa class, bukan tipe primitif. Deque<int> langsung ditolak compiler dengan pesan unexpected type, required: reference. Untuk menyimpan angka, pakai pasangan class-nya, yaitu Deque<Integer>. Java otomatis mengubah int jadi Integer dan sebaliknya, lewat fitur yang disebut autoboxing.

Kenapa Tidak Pakai Class Stack?

Java sebenarnya punya class bernama Stack, dan banyak tutorial lama masih memakainya. Tapi dokumentasi resmi class Stack sendiri menyarankan memakai Deque, karena operasi LIFO-nya lebih lengkap dan konsisten. Stack adalah class peninggalan versi awal Java, dan tetap ada supaya kode lama tidak rusak. Untuk kode baru, ArrayDeque pilihan yang lebih aman.

Kenalan Dulu: Apa Itu Queue di Java?

Queue adalah struktur data yang ditambah dari belakang dan diambil dari depan, persis seperti antrian di loket. Yang pertama masuk selalu jadi yang pertama keluar, sehingga prinsipnya disebut FIFO (first in, first out). Di Java, operasi utamanya offer untuk masuk antrian, poll untuk mengambil yang paling depan, dan peek untuk melihat siapa yang berikutnya.

Antrian di depan etalase warteg saat jam makan siang adalah contoh paling jelas. Orang yang baru datang berdiri di paling belakang, dan yang dilayani selalu yang paling depan. Kalau kasir melayani yang terakhir datang lebih dulu, bisa dipastikan ada yang protes.

ArrayDeque yang sama juga bisa dipakai sebagai queue. Bedanya ada di tipe variabel dan method yang dipanggil:

Queue<String> antrian = new ArrayDeque<>();
antrian.offer("Budi");
antrian.offer("Siti");
antrian.offer("Joko");

System.out.println(antrian);          // [Budi, Siti, Joko]
System.out.println(antrian.poll());   // Budi
System.out.println(antrian);          // [Siti, Joko]

Bandingkan dengan stack tadi. Di stack, yang keluar duluan adalah yang terakhir di-push. Di queue, yang keluar duluan adalah Budi, yang pertama di-offer.

Dua Gaya Method: Melempar Error atau Mengembalikan null

Interface Queue punya dua set method untuk pekerjaan yang sama. Bedanya ada di cara menangani antrian yang kosong:

Pekerjaan Melempar error kalau gagal Mengembalikan nilai khusus
Masuk antrian add(e) offer(e)
Ambil yang paling depan remove() poll() (hasilnya null kalau kosong)
Lihat yang paling depan element() peek() (hasilnya null kalau kosong)

Di kasir, antrian kosong itu kejadian biasa, bukan kesalahan. Jadi kita pakai offer, poll, dan peek, lalu cek hasilnya null atau tidak. Hal yang sama berlaku untuk stack: pop() pada tumpukan kosong langsung melempar NoSuchElementException, jadi cek dulu dengan isEmpty(). Ringkasan lengkap kedua gaya ini juga ada di tutorial The Queue Interface dari Oracle.

Perbedaan Stack dan Queue di Java

Stack dan queue sama-sama menyimpan data berurutan, tapi urutan keluarnya kebalikan. Pilih stack kalau yang paling baru harus ditangani lebih dulu, seperti membatalkan pesanan atau tombol undo. Pilih queue kalau yang paling lama menunggu harus didahulukan, seperti antrian pembeli atau daftar pesanan yang dimasak dapur.

Stack Queue
Prinsip LIFO, terakhir masuk keluar duluan FIFO, pertama masuk keluar duluan
Analogi warteg Tumpukan piring Antrian di depan etalase
Tipe variabel Deque<T> Queue<T>
Menambah push(e) offer(e)
Mengambil pop() poll()
Mengintip peek() peek()

Kenapa variabelnya ditulis sebagai Deque atau Queue, bukan langsung ArrayDeque? Karena tipe variabel menentukan method apa yang boleh dipanggil. Dengan Queue<String> antrian, kita tidak bisa tidak sengaja memanggil push dan merusak urutan antrian. Detailnya ada di tutorial The Deque Interface dari Oracle.

Kasir Warteg Canggih Versi 12

Kasir versi 12 memakai keduanya. Antrian pembeli disimpan di Queue<String>, dan pesanan setiap pembeli disimpan di Deque<ItemPesanan> sebagai stack, supaya pesanan terakhir bisa dibatalkan dengan mengetik -1.

Satu Class, Satu File

Di artikel Class dan Object, ketiga class masih ditulis di satu file. Mulai versi ini, setiap class punya file sendiri, seperti kebiasaan di proyek sungguhan:

12-stack-dan-queue-di-java/
├── WartegCanggih.java   (method main)
├── Menu.java
├── ItemPesanan.java     (baru)
└── Kasir.java

Karena kodenya tersebar di beberapa file, cara menjalankannya berubah. Jalan pintas java WartegCanggih.java dari artikel pertama hanya untuk program satu file, jadi tidak bisa dipakai lagi. Compile semua file dulu, baru jalankan class yang punya main, tanpa .java:

javac *.java
java WartegCanggih

Di JDK 22 ke atas, java WartegCanggih.java juga bisa dipakai untuk program multi-file berkat JEP 458. Seri ini memakai JDK 21, jadi tetap pakai javac.

Class ItemPesanan

Satu item di stack harus menyimpan dua hal: menu yang dipilih dan jumlah porsinya. Jadi kita buat class kecil, persis seperti Menu di artikel sebelumnya:

// satu kali pembeli menyebut pesanan: menu nomor sekian, sekian porsi
class ItemPesanan {
    int indexMenu;
    int porsi;

    ItemPesanan(int indexMenu, int porsi) {
        this.indexMenu = indexMenu;
        this.porsi = porsi;
    }
}

Pesanan sebagai Stack

ambilPesanan() sekarang menyimpan setiap pesanan ke stack. Kalau pembeli mengetik -1, pesanan paling atas diambil dengan pop(). Di akhir, isi stack diringkas jadi int[] porsi, supaya cetakStruk dan rekap harian dari versi 11 tetap bisa dipakai tanpa diubah:

int[] ambilPesanan() {
    Deque<ItemPesanan> pesanan = new ArrayDeque<>();   // LIFO: yang terakhir masuk, dibatalkan duluan
    int pilihan;
    do {
        System.out.print("Nomor menu (0 = selesai, -1 = batalkan terakhir): ");
        pilihan = bacaAngka();
        if (pilihan == 0) {
            continue;
        }
        if (pilihan == -1) {
            batalkanTerakhir(pesanan);
            continue;
        }
        // ... cek nomor menu dan jumlah porsi, sama seperti versi 11 ...
        pesanan.push(new ItemPesanan(index, jumlah));
        System.out.println("Dicatat: " + daftarMenu[index].nama + " x" + jumlah);
    } while (pilihan != 0);

    // ringkas isi stack jadi jumlah porsi per menu, untuk struk dan rekap
    int[] porsi = new int[daftarMenu.length];
    for (ItemPesanan item : pesanan) {
        porsi[item.indexMenu] += item.porsi;
    }
    return porsi;
}

void batalkanTerakhir(Deque<ItemPesanan> pesanan) {
    if (pesanan.isEmpty()) {
        System.out.println("Belum ada pesanan yang bisa dibatalkan.");
        return;
    }
    ItemPesanan batal = pesanan.pop();
    System.out.println("Dibatalkan: " + daftarMenu[batal.indexMenu].nama + " x" + batal.porsi);
}

Bandingkan dengan versi array di awal artikel. Tidak ada jumlahPesanan yang harus dijaga, tidak ada pergeseran elemen, dan tidak ada urusan array penuh. Pengecekan isEmpty() sebelum pop() adalah satu-satunya hal yang wajib diingat.

Antrian sebagai Queue

Antrian jadi field milik Kasir, karena harus bertahan sepanjang hari. Menu utama sekarang punya pilihan untuk menambah antrian, melayani yang paling depan, dan melihat isi antrian:

Queue<String> antrian = new ArrayDeque<>();   // FIFO: yang datang duluan, dilayani duluan

void tambahAntrian() {
    input.nextLine();                         // buang sisa Enter dari "Pilih"
    System.out.print("Atas nama siapa? ");
    String nama = input.nextLine().trim();
    if (nama.isEmpty()) {
        nama = "Pembeli";
    }
    antrian.offer(nama);
    System.out.println(nama + " masuk antrian ke-" + antrian.size() + ".");
}

void layaniAntrianBerikutnya() {
    String namaPembeli = antrian.poll();      // null kalau antrian kosong
    if (namaPembeli == null) {
        System.out.println("Antrian kosong, belum ada yang bisa dilayani.");
        return;
    }
    System.out.println("Melayani " + namaPembeli + ".");
    // ... tampilkan menu, ambil pesanan, cetak struk, terima pembayaran ...
}

Field antrian langsung diisi new ArrayDeque<>() di baris deklarasinya, tidak di constructor. Dua-duanya sah. Cara ini cocok untuk field yang nilai awalnya selalu sama dan tidak bergantung pada parameter constructor.

Ada satu aturan tambahan di menu utama: kasir menolak ditutup selama antrian belum kosong. Di case 0, kalau antrian.isEmpty() bernilai false, variabel pilihan diubah jadi -1 supaya perulangan do-while tidak berhenti. Kode lengkap keempat file ada di GitHub, link-nya di akhir artikel.

Contoh Satu Sesi

Budi dan Siti masuk antrian, kasir mencoba tutup tapi ditolak, lalu Budi membatalkan es tehnya. Ketikan pengguna ada di belakang tiap pertanyaan:

Pilih: 1
Atas nama siapa? Budi
Budi masuk antrian ke-1.
...
Pilih: 1
Atas nama siapa? Siti
Siti masuk antrian ke-2.
...
Pilih: 0
Masih ada 2 pembeli mengantre. Layani dulu.
...
Pilih: 2
Melayani Budi.
Daftar menu:
1. Nasi putih (Rp5000)
2. Tempe orek (Rp4000)
3. Es teh (Rp3000)
4. Telur balado (Rp6000)
5. Sayur asem (Rp4000)
Nomor menu (0 = selesai, -1 = batalkan terakhir): 2
Berapa porsi? 2
Dicatat: Tempe orek x2
Nomor menu (0 = selesai, -1 = batalkan terakhir): 1
Berapa porsi? 1
Dicatat: Nasi putih x1
Nomor menu (0 = selesai, -1 = batalkan terakhir): 3
Berapa porsi? 1
Dicatat: Es teh x1
Nomor menu (0 = selesai, -1 = batalkan terakhir): -1
Dibatalkan: Es teh x1
Nomor menu (0 = selesai, -1 = batalkan terakhir): 0

Pesanan atas nama Budi
Nasi putih     x1     Rp5000
Tempe orek     x2     Rp8000
----------------------------
Subtotal             Rp13000
Diskon                Rp1000
TOTAL                Rp12000

Yang dilayani pertama adalah Budi, karena dia yang pertama masuk antrian. Es teh yang dibatalkan tidak muncul di struk, dan nasi serta tempe tetap aman di tumpukan.

Error yang Sering Muncul Saat Memakai Stack dan Queue di Java

Hampir semua masalah stack dan queue bersumber dari satu hal: mengambil sesuatu dari wadah yang kosong. Bedanya hanya di cara Java memberi tahu, ada yang langsung error, ada yang diam-diam mengembalikan null.

  • NoSuchElementException dari pop() atau remove(). Terjadi kalau stack atau queue kosong. Cek dulu dengan isEmpty(), atau pakai poll() yang mengembalikan null.
  • NullPointerException setelah poll(). poll() tidak error saat antrian kosong, tapi hasil null-nya bisa meledak di baris berikutnya, misalnya saat dipanggil .trim(). Selalu cek null sebelum hasilnya dipakai.
  • NullPointerException saat offer(null). ArrayDeque tidak menerima null sebagai isi, karena null sudah dipakai sebagai tanda "kosong" oleh poll() dan peek(). Aturan ini tercantum di dokumentasi ArrayDeque.
  • unexpected type, required: reference. Muncul kalau menulis Deque<int>. Ganti dengan Deque<Integer>.
  • cannot find symbol: class Menu atau IllegalAccessError: failed to access class Menu. Muncul kalau kasir versi 12 dijalankan dengan java WartegCanggih.java. Cara itu hanya meng-compile satu file di memori, jadi Menu.java tidak ikut. Kalau di folder belum ada Menu.class, hasilnya cannot find symbol. Kalau sudah ada Menu.class dari javac sebelumnya, Menu dimuat lewat jalur yang berbeda dari WartegCanggih, dan Java menolak aksesnya. Solusinya sama: javac *.java, lalu java WartegCanggih.
  • Urutan terbalik. Kalau stack dibuat dengan offer lalu diambil dengan pop, atau queue dibuat dengan push, urutannya jadi kacau tanpa ada error. Menulis tipe variabel sebagai Queue<T> untuk antrian membantu mencegah ini.

Penutup

Yang menarik dari artikel ini, stack dan queue sebenarnya tidak menambah kemampuan baru. Semua yang mereka lakukan bisa dikerjakan dengan array dan penghitung, seperti di awal artikel. Yang mereka tambahkan adalah batasan: stack hanya boleh diambil dari atas, queue hanya boleh dari depan. Justru karena dibatasi, kita tidak perlu lagi menjaga urutan secara manual, dan kesalahan seperti melayani orang yang salah jadi jauh lebih susah terjadi.

Sebagai latihan, coba tambahkan pilihan 5. Lihat pembeli berikutnya di menu utama, yang hanya menampilkan nama paling depan tanpa mengeluarkannya dari antrian. Petunjuknya ada di tabel tadi: method mana yang mengintip tanpa mengambil? Kalau sudah, coba pikirkan juga: bagaimana kalau Budi ingin membatalkan nasi putih, bukan es teh? Stack tidak dirancang untuk itu, dan kamu akan butuh struktur data lain seperti ArrayList.

Di artikel berikutnya, Pilar OOP di Java: Encapsulation, Inheritance, Polymorphism, dan Abstraction, yang terbit Selasa, 3 November 2026, pemilik warteg datang dengan tiga permintaan baru: harga yang tidak boleh diubah sembarangan, Paket Hemat, dan diskon jam sepi. Ketiganya dijawab oleh empat pilar OOP, termasuk alasan kenapa tadi kita menulis Deque<String> dan bukan ArrayDeque<String>.

Daftar semua artikel seri ini ada di label Warteg Canggih Series. Di luar warteg, pernah lihat contoh stack atau queue di aplikasi yang kamu pakai sehari-hari? Tombol kembali di browser dan antrian cetak di printer bisa jadi petunjuk. Cerita di kolom komentar.

Source code lengkap artikel ini bisa dilihat di GitHub: 12-stack-dan-queue-di-java

FAQ

Apa perbedaan stack dan queue di Java? Stack memakai prinsip LIFO, jadi elemen yang terakhir dimasukkan adalah yang pertama diambil. Queue memakai prinsip FIFO, jadi elemen yang pertama dimasukkan adalah yang pertama diambil. Di Java, keduanya bisa dibuat dengan ArrayDeque, dan bedanya ada di method yang dipakai.

Kenapa ArrayDeque lebih disarankan daripada class Stack? Dokumentasi resmi class Stack sendiri menyarankan Deque karena operasi LIFO-nya lebih lengkap dan konsisten. Stack adalah class peninggalan versi awal Java yang tetap dipertahankan untuk kode lama. Untuk kode baru, ArrayDeque lebih disarankan.

Apa bedanya poll() dan remove() pada queue? Keduanya mengambil elemen paling depan. Bedanya, remove() melempar NoSuchElementException kalau queue kosong, sedangkan poll() mengembalikan null. Pakai poll() kalau kosong adalah kejadian biasa, dan jangan lupa cek hasilnya null atau tidak.

Kenapa Deque<int> error di Java? Generics hanya menerima class, bukan tipe primitif seperti int. Ganti dengan Deque<Integer>. Java otomatis mengubah int jadi Integer dan sebaliknya lewat autoboxing, jadi kode untuk menambah dan mengambil angkanya tetap sama.

Kapan memakai stack dan kapan memakai queue? Pakai stack kalau yang paling baru harus ditangani lebih dulu, misalnya fitur batal atau undo. Pakai queue kalau yang paling lama menunggu harus didahulukan, misalnya antrian pembeli atau daftar tugas yang dikerjakan berurutan.

Referensi

Posting Komentar untuk "Stack dan Queue di Java: Konsep LIFO dan FIFO"