1. Wprowadzenie
Kiedyś omawialiśmy już „nieskończone tablice” – listy. Pogłębmy naszą wiedzę i zadajmy sobie jeszcze raz pytanie, czym jest „klasyczna” tablica. Jeśli się przyjrzeć, przypomina skrzynkę na narzędzia z ustaloną liczbą przegródek. Załóżmy, że w skrzynce jest 10 komórek. Pojawia się 11. narzędzie – i co teraz? Trzeba kupić nową skrzynkę! Tak samo tradycyjna tablica ma stały rozmiar i po utworzeniu jej długość już się nie zmieni.
Jeśli trzeba dodać lub usunąć element, trzeba tworzyć nową tablicę i ręcznie kopiować dane. W tablicy łatwo i szybko można znaleźć element po indeksie, ale wstawienie elementu w środek tablicy to nie jest po prostu „wstawić”, tylko „przesunąć wszystko w prawo”, a przy usuwaniu – „przesunąć wszystko w lewo”. To jest wolne i niewygodne. Ponadto tablica nie przechowuje żadnej dodatkowej „logiki”: to tylko zestaw komórek, a sortowanie, wyszukiwanie po zawartości czy sprawdzanie unikalności trzeba realizować zewnętrznymi środkami.
Przykład: dynamiczna lista studentów
Załóżmy, że piszesz aplikację do ewidencji studentów w grupie. Na początku w grupie jest 5 osób, potem doszedł jeszcze ktoś, potem ktoś odszedł. Z tablicą wyglądałoby to tak:
String[] students = new String[5];
students[0] = "John";
students[1] = "Maria";
// i tak dalej...
// Ojej, przyszedł jeszcze jeden student
// Trzeba utworzyć nową tablicę!
String[] newStudents = new String[6];
for (int i = 0; i < students.length; i++) {
newStudents[i] = students[i];
}
newStudents[5] = "Alex";
students = newStudents;
Wygodne? Delikatnie mówiąc, nie. A jeśli takich operacji jest dużo? Aż prosi się o coś wygodniejszego...
2. Czym jest kolekcja?
Kolekcja to obiekt, który służy jako kontener do przechowywania grupy innych obiektów (elementów). Kolekcje pozwalają dodawać, usuwać, iterować po elementach, a także wykonywać inne operacje: wyszukiwanie, sortowanie, filtrowanie itd.
W Javie wszystkie kolekcje implementują lub dziedziczą po interfejsie Collection (albo – dla odwzorowań – po Map). Kolekcja to nie po prostu „kupa czegoś”, lecz struktura, która udostępnia wygodny, przemyślany zestaw metod do pracy z elementami.
Dlaczego kolekcje to obiekty?
Ponieważ kolekcje są zaimplementowane jako klasy, a więc możesz tworzyć kolekcje dowolnych obiektów, łączyć je, dziedziczyć, rozszerzać, używać w swoich klasach i metodach.
Przykład:
import java.util.ArrayList;
import java.util.List;
List<String> students = new ArrayList<>();
students.add("John");
students.add("Maria");
students.add("Alex");
Voilà! Teraz możesz dodawać tylu studentów, ilu chcesz, nie martwiąc się o rozmiar tablicy.
3. Typowe zadania rozwiązywane przez kolekcje
Kolekcje to szwajcarski scyzoryk do pracy z danymi. Oto jakie zadania pozwalają rozwiązywać:
- Przechowywanie dynamicznej listy danych: Na przykład lista studentów, zadania w planerze, wiadomości na czacie.
- Wyszukiwanie i filtrowanie: Szybko znaleźć element, sprawdzić jego obecność, pobrać wszystkie elementy spełniające określony warunek.
- Sortowanie: Łatwo posortować elementy według potrzebnego kryterium.
- Usuwanie i dodawanie elementów: Wstawianie i usuwanie elementów w dowolnym miejscu kolekcji bez konieczności ręcznego kopiowania tablic.
- Grupowanie po kluczu: Na przykład książka telefoniczna, gdzie każdemu imieniu odpowiada numer telefonu.
- Gwarancja unikalności: Na przykład zbiór wszystkich unikalnych słów w tekście.
Przykład: książka telefoniczna
Z tablicą:
- Jak znaleźć numer po imieniu? Trzeba przeglądać tablicę i porównywać imiona.
- Jak dodać nową parę? Trzeba rozszerzać tablicę.
- Jak zagwarantować, że imiona się nie powtarzają? To jeszcze trudniejsze.
Z kolekcją:
- Używamy Map<String, String> – i wszystko działa „od ręki”.
4. Przegląd podstawowych rodzajów kolekcji
W Javie kolekcje dzielą się na trzy główne grupy:
| Rodzaj kolekcji | Interfejs/Klasa | Do czego służy |
|---|---|---|
| Lista | List, ArrayList | Uporządkowany zbiór elementów, dopuszcza duplikaty, dostęp po indeksie |
| Zbiór | Set, HashSet | Przechowuje tylko unikalne elementy, kolejność nie jest gwarantowana |
| Mapa | Map, HashMap | Przechowuje pary klucz–wartość, szybkie wyszukiwanie po kluczu |
Listy (List)
- Uporządkowane kolekcje, dopuszczają duplikaty.
- Można pobrać element po indeksie.
- Przykłady: ArrayList, LinkedList.
Zbiory (Set)
- Przechowują tylko unikalne elementy.
- Brak dostępu po indeksie.
- Przykłady: HashSet, TreeSet.
Mapy (Map)
- Przechowują pary klucz–wartość.
- Szybkie wyszukiwanie po kluczu.
- Przykłady: HashMap, TreeMap.
Schemat poglądowy (mocno uproszczony):
+------------------+ +-------------------+ +---------------------+
| List | | Set | | Map |
|------------------| |-------------------| |---------------------|
| [a, b, c, d, a] | | {a, b, c, d} | | {a=1, b=2, c=3} |
| Indeksowanie: jest| | Indeksowanie: brak| | Wyszukiwanie po kluczu|
| Duplikaty: tak | | Duplikaty: nie | | Klucze unikalne |
+------------------+ +-------------------+ +---------------------+
5. Przydatne szczegóły
Kiedy użyć której kolekcji?
List – gdy ważna jest kolejność elementów, potrzebne są duplikaty, potrzebny jest dostęp po indeksie (np. lista zadań, historia wiadomości).
Set – gdy potrzebne są tylko unikalne elementy, a kolejność nie ma znaczenia (np. zbiór unikalnych użytkowników).
Map – gdy trzeba odwzorować klucze na wartości (np. książka telefoniczna, gdzie imię to klucz, a telefon to wartość).
Analogii z życia
List – kolejka w stołówce: kto pierwszy przyszedł, ten pierwszy obsłużony; można stanąć w kolejce kilka razy (duplikaty).
Set – lista gości na imprezie: każdy gość tylko raz (unikalność).
Map – książka adresowa: każde imię ma swój numer telefonu.
Krótka ściągawka: kolekcje vs tablice
| Tablica (int[]) | Kolekcja (List<Integer>) | |
|---|---|---|
| Rozmiar | Stały | Dynamiczny |
| Dodawanie elementu | Niewygodne | Łatwo: add() |
| Usuwanie elementu | Niewygodne | Łatwo: remove() |
| Wyszukiwanie po wartości | Ręczne przeszukiwanie | Metody: contains(), itp. |
| Sortowanie | Przez Arrays.sort() | Przez Collections.sort(), metody kolekcji |
| Wymuszanie unikalności | Brak | Przez Set |
| Pary klucz–wartość | Brak | Przez Map |
6. Związek kolekcji z programowaniem obiektowym
Kolekcje to obiekty implementujące określone interfejsy (List, Set, Map). To znaczy, że możesz:
- Przechowywać w kolekcjach dowolne obiekty, w tym egzemplarze własnych klas.
- Tworzyć kolekcje kolekcji (np. listę list).
- Używać kolekcji jako parametrów i wartości zwracanych metod.
- Rozszerzać funkcjonalność kolekcji za pomocą dziedziczenia i kompozycji.
Przykład: kolekcja obiektów twojej klasy
import java.util.ArrayList;
import java.util.List;
class Student {
String name;
int age;
// Konstruktor, gettery/settery itd.
}
public class Main {
public static void main(String[] args) {
List<Student> group = new ArrayList<>();
group.add(new Student("John", 20));
group.add(new Student("Maria", 21));
// i tak dalej...
}
}
7. Typowe błędy podczas pracy z kolekcjami
Błąd nr 1: używanie kolekcji bez typu (raw types).
Jeśli napiszesz ArrayList list = new ArrayList(), to przy dodawaniu dowolnego obiektu (np. naprzemiennie napisów i liczb) kompilator nie będzie protestował, ale później, przy próbie pobrania elementu i rzutowania go na potrzebny typ, możesz dostać błąd czasu wykonania (ClassCastException). Zawsze używaj generyków: ArrayList<String> list = new ArrayList<>().
Błąd nr 2: zapomniany import wymaganej klasy.
Jeśli widzisz błąd „cannot find symbol”, sprawdź, czy na początku pliku jest linia import java.util.ArrayList; lub odpowiedni import dla twojej kolekcji.
Błąd nr 3: mylenie kolekcji z tablicami.
Kolekcja to nie tablica! Kolekcja nie ma pola length – zamiast tego używaj metody size(). Tablica nie ma metody add(), a kolekcja nie ma operatora [] do dostępu po indeksie (tylko listy przez get(index)).
Błąd nr 4: założenie, że kolejność elementów zawsze jest zachowana.
Jeśli używasz Set lub Map, kolejność elementów nie jest gwarantowana (chyba że użyjesz specjalnych implementacji, takich jak LinkedHashSet lub TreeMap). Dla danych uporządkowanych używaj List lub odpowiednich kolekcji.
Błąd nr 5: używanie typów prymitywnych w kolekcjach.
Kolekcje mogą przechowywać tylko obiekty, a nie prymitywy. To znaczy, że nie można utworzyć List<int>, tylko List<Integer>. Pamiętaj o klasach opakowujących!
GO TO FULL VERSION