1. Klasa ArrayList
W języku Java interfejs List — to kontrakt: „Jestem uporządkowaną kolekcją elementów i można pobrać element po numerze (indeksie)”.
Kluczowe właściwości listy (List):
- Elementy są przechowywane w określonej kolejności (w przeciwieństwie do zbioru).
- Można przechowywać duplikaty (na przykład: dwa razy "Jan").
- Można odwołać się do elementu po indeksie: pierwszy element — indeks 0, drugi — 1 i tak dalej.
- Można dodawać i usuwać elementy w dowolnym miejscu listy.
Najpopularniejszą implementacją interfejsu List jest ArrayList. Pod spodem używa zwykłej tablicy i automatycznie się rozszerza wraz ze wzrostem danych.
Jak utworzyć ArrayList?
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
// Tworzymy listę napisów
List<String> students = new ArrayList<>();
}
}
Wyjaśnienie
- List<String> — to zmienna typu „lista napisów”. Zmienną deklarujemy przez interfejs, a obiekt tworzymy przez konkretną klasę (ArrayList).
- Nawiasy kątowe <String> określają typ elementów listy (generyki, generics).
Podstawowe metody ArrayList (i w ogóle dowolnej List):
- add(element) — dodać element na końcu listy.
- add(index, element) — wstawić element pod konkretnym indeksem.
- get(index) — pobrać element po indeksie.
- set(index, element) — zastąpić element pod indeksem.
- remove(index) — usunąć element po indeksie.
- remove(Object) — usunąć pierwsze znalezione wystąpienie, równe przekazanemu obiektowi.
- size() — sprawdzić liczbę elementów w liście.
Przykład: pracujemy z listą studentów
import java.util.ArrayList;
import java.util.List;
public class StudentListDemo {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
// Dodajemy studentów
students.add("Jan");
students.add("Piotr");
students.add("Maria");
// Wypisujemy wszystkich studentów
System.out.println("Lista studentów: " + students);
// Pobieramy pierwszego studenta
String first = students.get(0);
System.out.println("Pierwszy student: " + first);
// Zmieniamy imię drugiego studenta
students.set(1, "Paweł");
System.out.println("Po zmianie: " + students);
// Usuwamy Marię
students.remove("Maria");
System.out.println("Po usunięciu Marii: " + students);
// Rozmiar listy
System.out.println("Łącznie studentów: " + students.size());
}
}
Rezultat:
Lista studentów: [Jan, Piotr, Maria]
Pierwszy student: Jan
Po zmianie: [Jan, Paweł, Maria]
Po usunięciu Marii: [Jan, Paweł]
Łącznie studentów: 2
Jak to się ma do naszej aplikacji?
Załóżmy, że mamy aplikację do zarządzania zadaniami studenta. Teraz możemy przechowywać listę zadań jako List<String>, a nie jako tablicę, i dynamicznie dodawać nowe zadania.
2. Klasa LinkedList: gdy liczy się szybkość wstawiania i usuwania
LinkedList — alternatywna implementacja listy. Zbudowana jako lista dwukierunkowa: każdy węzeł zna poprzedni i następny element. Jak w składzie pociągu: wstawienie „wagonu” pośrodku jest szybkie i nie wymaga przebudowy całego składu.
Tworzenie LinkedList
import java.util.LinkedList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
}
}
Cechy LinkedList
- Szybkie wstawianie i usuwanie na początku i w środku listy.
- Wolny dostęp po indeksie (aby znaleźć 100. element, trzeba przejść po łańcuchu).
- Odpowiedni, jeśli często dodajesz/usuwasz elementy nie tylko na końcu, ale także na początku lub w środku.
Przykład: użycie LinkedList
import java.util.LinkedList;
import java.util.List;
public class TaskListDemo {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
tasks.add("Obudzić się");
tasks.add("Zjeść śniadanie");
tasks.add("Iść na zajęcia");
// Dodajemy zadanie na początek
tasks.add(0, "Ustawić budzik");
System.out.println("Lista zadań: " + tasks);
// Usuwamy pierwsze zadanie (najwcześniejsze)
tasks.remove(0);
System.out.println("Po usunięciu pierwszego zadania: " + tasks);
}
}
3. Porównanie ArrayList i LinkedList
| Kryterium | |
|
|---|---|---|
| Podstawa | Tablica | Lista dwukierunkowa |
| Szybki dostęp po indeksie | Tak (O(1)) | Nie (O(n)) |
| Szybkie wstawianie/usuwanie na początku/w środku | Nie (O(n)) | Tak (O(1) — jeśli jest referencja) |
| Szybkie wstawianie/usuwanie na końcu | Tak (zwykle O(1)) | Tak (O(1)) |
| Pamięć | Zużywa mniej pamięci | Więcej (dodatkowe referencje do sąsiadów) |
| Typowe przypadki użycia | Częsty dostęp po indeksie | Częste wstawianie/usuwanie |
Prosta zasada:
- Potrzebujesz szybkiego dostępu po numerze — użyj ArrayList.
- Często dodajesz/usuwasz na początku lub w środku — użyj LinkedList.
4. Typowe operacje na listach
Iteracja po elementach listy
Zwykła pętla for
for (int i = 0; i < students.size(); i++) {
System.out.println("Student #" + i + ": " + students.get(i));
}
Pętla for-each (najpopularniejszy sposób)
for (String name : students) {
System.out.println("Imię: " + name);
}
Wyrażenie lambda (Java 8+)
students.forEach(name -> System.out.println("Imię: " + name));
Wyszukiwanie elementów
- contains(element) — zwraca true, jeśli element jest na liście.
- indexOf(element) — zwraca indeks pierwszego wystąpienia elementu lub -1, jeśli nie znaleziono.
if (students.contains("Jan")) {
System.out.println("Jan jest na liście!");
}
int index = students.indexOf("Jan");
System.out.println("Indeks Jana: " + index);
Czyszczenie listy
clear() — usuwa wszystkie elementy.
students.clear();
System.out.println("Lista po wyczyszczeniu: " + students);
5. Kiedy używać ArrayList, a kiedy LinkedList?
Najprościej myśleć tak: ArrayList jest dobry tam, gdzie potrzebny jest szybki dostęp do elementów po indeksie, a lista zmienia się niezbyt często. Na przykład długa lista użytkowników lub historia wiadomości — głównie odczyt.
LinkedList z kolei przydaje się, gdy stale wstawiasz lub usuwasz elementy na początku/w środku. Może to być kolejka, stos albo historia cofania.
W praktyce najczęściej używa się ArrayList. A LinkedList pozostaje „narzędziem na wszelki wypadek”: leży w szufladzie, ale czasem okazuje się dokładnie tym, czego trzeba.
6. Typowe błędy przy pracy z listami
Błąd nr 1: wyjście poza zakres listy. Najczęstsza sytuacja — odwołanie do nieistniejącego indeksu. Jeśli lista ma trzy elementy, a napiszesz students.get(5), otrzymasz IndexOutOfBoundsException. Przed dostępem sprawdzaj size().
Błąd nr 2: usuwanie elementu podczas iteracji. Przy iteracji przez for-each i jednoczesnym usuwaniu elementów wystąpi ConcurrentModificationException. Do złożonych usunięć używaj pętli po indeksie lub Iterator.
Błąd nr 3: nieprawidłowe porównywanie obiektów. Jeśli przechowujesz własne obiekty (na przykład Student), metody contains i remove opierają się na equals. Jeśli go nie nadpiszesz, porównanie będzie po referencji, a nie po zawartości.
Błąd nr 4: używanie surowych typów. Nie pisz List list = new ArrayList() — zawsze podawaj typ elementów: List<String> list = new ArrayList<>(). Generyki chronią przed błędami i czynią kod czytelniejszym.
GO TO FULL VERSION