Przejdź do treści

Kolejki, stosy i Deque w praktyce

  • Kolekcje i strumienie
  • poziom: średni
  • aktualizacja: 16 sierpnia 2026

Do stosu i kolejki służy w Javie jedna klasa: ArrayDeque. Ma push, pop i peek jak stos, ma też offer i poll jak kolejka FIFO, a w obu rolach jest szybsza od LinkedList, bo trzyma elementy w tablicy zamiast w łańcuchu obiektów. Stara klasa Stack nadal działa, ale dziedziczy po Vector synchronizację każdej metody, za którą płacisz nawet w jednym wątku — a wydruk demo pokazuje przy okazji drugi powód, żeby jej nie wybierać. Ten skrypt składa z tych klocków trzy typowe zadania: historię cofania z limitem kroków, kolejkę zadań do przetworzenia i wybór trzech najdroższych pozycji cennika. Po drodze celowo wywołuje metody tak, żeby zawiodły: remove() na pustej kolejce, add() na pełnej i offer(null) na ArrayDeque — bo to właśnie te trzy zachowania najczęściej zaskakują na produkcji.

JAVA KolejkiIStosy.java 130 linii Pobierz .java
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Stack;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * Kolejki, stosy i Deque w praktyce — skryptyJAVA.pl
 * Zapisz jako: KolejkiIStosy.java
 */
public class KolejkiIStosy {

    /** Pozycja cennika; cena w groszach, żeby nie liczyć pieniędzy na double. */
    public record Pozycja(String nazwa, int cenaGrosze) {
        @Override
        public String toString() {
            return String.format("%s %d,%02d zł", nazwa, cenaGrosze / 100, cenaGrosze % 100);
        }
    }

    /** Dokłada stan na wierzchołek historii; najstarszy wypada z przeciwnego końca. */
    public static void zapamietaj(Deque<String> historia, String stan, int limit) {
        historia.push(stan);
        while (historia.size() > limit) {
            historia.removeLast();
        }
    }

    /** Zdejmuje ostatni stan; przy pustej historii zwraca null zamiast rzucać wyjątkiem. */
    public static String cofnij(Deque<String> historia) {
        return historia.poll();
    }

    /** Przetwarza zadania w kolejności zgłoszenia i zwraca liczbę obsłużonych. */
    public static int przetworzZadania(Queue<String> zadania) {
        int obsluzone = 0;
        // Warunek „aż do null” niesie założenie: kolejka nie wpuszcza null-i. LinkedList je łamie.
        for (String zadanie = zadania.poll(); zadanie != null; zadanie = zadania.poll()) {
            System.out.println("    wykonuję: " + zadanie + " (zostało " + zadania.size() + ")");
            obsluzone++;
        }
        return obsluzone;
    }

    /** Kolejka priorytetowa ustawiająca na czele najdroższą pozycję. */
    public static PriorityQueue<Pozycja> wedlugCenyMalejaco(List<Pozycja> pozycje) {
        Comparator<Pozycja> odNajdrozszych = Comparator.comparingInt(Pozycja::cenaGrosze).reversed();
        PriorityQueue<Pozycja> kolejka = new PriorityQueue<>(odNajdrozszych);
        kolejka.addAll(pozycje);
        return kolejka;
    }

    /** Zdejmuje kolejne elementy kolejki — priorytet respektuje wyłącznie poll(). */
    public static List<Pozycja> zdejmij(Queue<Pozycja> kolejka, int ile) {
        List<Pozycja> wynik = new ArrayList<>();
        for (int i = 0; i < ile && !kolejka.isEmpty(); i++) {
            wynik.add(kolejka.poll());
        }
        return wynik;
    }

    // ── PRZYKŁADY ───────────────────────────────────────────
    private static void demoStosu() {
        System.out.println("== Stos: historia cofania, limit 3 kroki ==");
        Deque<String> historia = new ArrayDeque<>();
        List.of("pusty dokument", "tytuł", "akapit", "tabela")
                .forEach(stan -> zapamietaj(historia, stan, 3));
        Stack<String> staryStos = new Stack<>();
        historia.descendingIterator().forEachRemaining(staryStos::push);
        System.out.println("ArrayDeque wypisuje od wierzchołka: " + historia);
        System.out.println("Stack ten sam stos, ale od dna:     " + staryStos);
        System.out.println("Cofam " + cofnij(historia) + ", na wierzchu zostaje " + historia.peek());
    }

    private static void demoKolejki() {
        System.out.println(System.lineSeparator() + "== Kolejka zadań i dwie rodziny metod ==");
        Queue<String> zadania = new ArrayDeque<>(List.of("wyślij fakturę", "przelicz raport"));
        zadania.offer("wyczyść cache");
        System.out.println("Na czele: " + zadania.peek() + ", czeka zadań: " + zadania.size());
        System.out.println("Obsłużono zadań: " + przetworzZadania(zadania));
        System.out.println("Pusta: poll() = " + zadania.poll() + ", peek() = " + zadania.peek());
        try {
            zadania.remove();
        } catch (NoSuchElementException e) {
            System.out.println("Pusta: remove() rzuca " + e.getClass().getSimpleName());
        }
        // Na ArrayDeque add i offer robią to samo — różnicę widać dopiero na kolejce z limitem.
        Queue<String> ciasna = new ArrayBlockingQueue<>(1);
        ciasna.offer("pierwsze");
        System.out.println("Pełna: offer() = " + ciasna.offer("drugie"));
        try {
            ciasna.add("drugie");
        } catch (IllegalStateException e) {
            System.out.println("Pełna: add() rzuca " + e.getClass().getSimpleName());
        }
        try {
            new ArrayDeque<String>().offer(null);
        } catch (NullPointerException e) {
            System.out.println("ArrayDeque.offer(null) rzuca " + e.getClass().getSimpleName());
        }
        // LinkedList null przyjmie — i właśnie tym psuje pętlę „pobieraj, aż wyjdzie null”.
        Deque<String> zNullem = new LinkedList<>(List.of("wyślij fakturę"));
        zNullem.offerFirst(null);
        System.out.println("LinkedList z null-em: obsłużono " + przetworzZadania(zNullem)
                + " zadań, a w kolejce nadal czeka: " + zNullem.peek());
    }

    private static void demoPriorytetow() {
        System.out.println(System.lineSeparator() + "== PriorityQueue: najdroższe pozycje ==");
        List<Pozycja> cennik = List.of(new Pozycja("Kawa", 8990), new Pozycja("Filtry", 1250),
                new Pozycja("Termometr", 12900), new Pozycja("Ekspres", 249900),
                new Pozycja("Waga", 32900), new Pozycja("Młynek", 45900));
        PriorityQueue<Pozycja> wgCeny = wedlugCenyMalejaco(cennik);
        System.out.println("Surowy toString: " + wgCeny);
        System.out.println("Kolejne poll():  " + zdejmij(wgCeny, cennik.size()));
        System.out.println("Trzy najdroższe: " + zdejmij(wedlugCenyMalejaco(cennik), 3));
    }

    public static void main(String[] args) {
        demoStosu();
        demoKolejki();
        demoPriorytetow();
    }
}

Jak użyć tego skryptu

  1. Wklej kod do pliku nazwanego KolejkiIStosy.java — tak samo brzmi tu klasa publiczna — i odpal go poleceniem java KolejkiIStosy.java. Wydruk dzieli się na trzy części: stos z historią cofania, kolejkę zadań wraz z reakcją na błędne wywołania oraz kolejkę priorytetową.
  2. Na stos bierz Deque<String> stos = new ArrayDeque<>() i trzymaj się trójki push, pop, peek. Nie mieszaj jej z add: na Deque add dokłada na koniec, a push na początek — po takiej pomyłce kod nadal się kompiluje, tylko stos zachowuje się jak kolejka.
  3. Kolejkę zadań deklaruj jako Queue<String>, wkładaj przez offer, pobieraj przez poll. Metodę przetworzZadania możesz przenieść do siebie bez zmian, ale tylko dla kolejki, która nie wpuszcza null-i — jej pętla kończy pracę na pierwszym null z poll, więc karmiona LinkedList po cichu zostawi resztę zadań (ostatnia linijka drugiej sekcji wydruku).
  4. Po kilka największych elementów sięgaj przez wedlugCenyMalejaco i zdejmij; żeby zmienić kryterium, podmień sam komparator, na przykład na Comparator.comparing(Pozycja::nazwa). Kolejka priorytetowa opłaca się wtedy, gdy z dużego zbioru bierzesz tylko czubek — jeśli potrzebujesz całej listy w porządku, czytelniejsze i tak samo szybkie będzie lista.stream().sorted(komparator).toList().

Warto wiedzieć

  • Iterator java.util.Stack idzie od dna stosu, a nie od wierzchołka — dotyczy to także pętli for-each, toString i strumienia. Kod, który „przegląda historię od najnowszego wpisu”, po cichu pokaże najstarsze. W wydruku widać oba porządki obok siebie: te same trzy stany, ArrayDeque od wierzchołka, Stack od dna. Nie ma na to przełącznika: jeśli musisz zostać przy Stack, przejdź go ręcznie przez listIterator(stos.size()) i cofaj się pętlą while (it.hasPrevious()).
  • Kolejka ma dwie rodziny metod: add, remove i element rzucają wyjątek, a offer, poll i peek zwracają false albo null. Na ArrayDeque, która nie ma limitu, add i offer zachowują się przy wstawianiu identycznie — dlatego demo sięga po ArrayBlockingQueue o pojemności 1, żeby pokazać offer() = false obok IllegalStateException z add(). Różnica ujawnia się dopiero, gdy podmienisz implementację na kolejkę z limitem.
  • PriorityQueue gwarantuje porządek wyłącznie na czole kolejki. toString, iterator, forEach i stream pokazują surową tablicę kopca — w wydruku najtańsze „Filtry 12,50 zł” stoją na czwartym miejscu, przed droższym „Termometrem”. Jedyny wiarygodny porządek daje ciąg wywołań poll, dlatego robi to metoda zdejmij. Pamiętaj tylko, że zdejmij opróżnia przekazaną kolejkę: po wypisaniu wszystkich pozycji wgCeny jest już puste, więc demo buduje kopiec drugi raz.
  • ArrayDeque, PriorityQueue i kolejki blokujące odrzucają null wyjątkiem NullPointerException, a LinkedList go przyjmuje. To nie jest drobiazg. W kolejce z null wynik poll() == null przestaje znaczyć „kolejka pusta”, więc pętla z przetworzZadania kończy się natychmiast — w ostatniej linijce drugiej sekcji raportuje 0 obsłużonych zadań, choć „wyślij fakturę” dalej stoi w kolejce. LinkedList w roli kolejki wybieraj więc tylko wtedy, gdy naprawdę potrzebujesz null-i albo usuwania ze środka przez ListIterator; w pozostałych przypadkach ArrayDeque jest szybsza i zużywa mniej pamięci, bo nie opakowuje każdego elementu w osobny węzeł.
#kolekcje #deque #comparator #sortowanie #wydajność

Podobne skrypty