Dzisiejsza lekcja poświęcona jest kolekcjom — specjalnej grupie klas, które służą do tego, żeby przechowywać zbiory elementów.
Sprawne posługiwanie się kolekcjami to jedna z ważniejszych umiejętności każdego programisty. W tej lekcji poznamy podstawowe rodzaje kolekcji, różnice między nimi oraz jak ich używać. W języku Java wszystkie kolekcje rozszerzają klasę Collection (tajemniczy zapis <E> wyjaśnię w dalszej części lekcji).
Lekcja
Przede wszystkim zacznijmy od wyjaśnienia czym jest tajemnicze <E> w dokumentacji kolekcji. To wskazuje, że mamy do czynienia z tzw. generykami. O samych generykach powiemy sobie więcej w jednej z dodatkowych lekcji, ale na tą chwilę dwa słowa, co to znaczy w kontekście kolekcji.
Kolekcje mamy zdefiniowane jako Collection<E>, same metody też mają sygnatury np. add(E element). Oznacza to, że możemy wskazać jakiego typu dane będziemy przechowywali w tej kolekcji. Jednocześnie metody będą przyjmowały tylko argumenty danego typu. Sprytne, prawda? Dlatego kolekcja określona jako Collection<String> będzie miała metodę add(String element), natomiast kolekcja zdefiniowana jako Collection<Kot> będzie miała metodę add(Kot element).
Tyle o generykach na ten moment, zapraszam oczywiście do dodatkowej lektury (w sekcji linków znajdziesz więcej informacji). Przejdźmy teraz do rodzajów kolekcji.
Rodzaje kolekcji w Javie
W Javie wyróżniamy trzy podstawowe rodzaje kolekcji które znacząco różnią się od siebie właściwościami. Każdy z tych rodzajów ma kilka implementacji, które z kolei różnią się szczególnymi cechami (np. jedne są lepsze do szybkiego dostępu, inne do dodawania itp). Poniżej w skrócie opis głównych różnic, a w dalszej części znajdziesz szczegółowy opis każdej z nich:
- java.util.List — lista, tj. kolekcja o określonej pozycji (nie mylmy z sortowaniem!). Możemy odwołać się do elementu po numerze kolejnym, np. ‘podaj element na pozycji 1’. Ten sam obiekt może występować na kilku pozycjach
- java.util.Set — zbiór, tj. kolekcja, która przechowuje obiekty bez określenia pozycji (ale może przechowywać je w sposób uporządkowany — tj. posortowany). Ten sam obiekt może występować tylko raz w danej kolekcji.
- java.util.Queue — kolejka, czyli lista umożliwiająca implementację kolejek FIFO i FILO. Kolejki działają analogicznie jak w sklepie, elementy dodawane trafiają na koniec kolejki, możemy najpierw ‘obsłużyć’ osobę z początku kolejki (FIFO) lub z jej końca (FILO)
- java.util.Map — mapa, nie jest to stricte kolekcja, ale jako taką będziemy ją traktować. Mapa przechowuje mapowania klucz-wartość, przy czym klucz musi być unikalny. Można o niej myśleć jak o indeksie czy spisie treści (spis treści mapuje nazwę rozdziału na rozdział, gdzie nazwa rozdziału jest kluczem a sam rozdział (jego treść) wartością)
java.util.List
Lista to kolekcja, w której każdy element ma przyporządkowany numer kolejny (indeks). Podstawowe operacje na liście to dodaj element ( add(E element) ) oraz pobierz element z pozycji i ( E get(int position) ).
Obiekty na liście mogą się powtarzać — tj. ten sam obiekt może się pojawić na liście kilka razy (np. na pozycjach 0, 1 i 2)
Najpopularniejsze implementacje
- ArrayList — przechowuje dane wewnętrznie w tablicy. Opcja optymalna jeśli znamy docelowy rozmiar listy lub operacji dodawania wykonujemy mało w stosunku do operacji odczytu, a operacje odczytu nie są w pętli for-each
- LinkedList — przechowuje dane w postaci powiązanej, wydajniejsza w sytuacjach w których dodajemy wiele elementów, a odczyt odbywa się sekwencyjnie (odczytujemy elementy za pomocą iteratora lub pętli for-each)
Przykładowy kod
List<String> lista = new ArrayList<String>();
lista.add("pierwszy");
lista.add("drugi");
System.out.println(lista.get(1)); //wypisze "drugi"
java.util.Set
Set (zbiór) to kolekcja, w której elementy nie mają przyporządkowanego numeru kolejnego (indeksu), a dostęp do nich odbywa się za pośrednictwem iteratora. Podstawowe operacje na tym zbiorze to dodaj element ( add(E element) ) oraz pobierz itertor ( Iterator<E> iterator() ).
Obiekty w zbiorze nie mogą się powtarzać — tj. ten sam obiekt można dodać do zbioru wielokrotnie, ale będzie on w nim przechowywany tylko jeden raz
Najpopularniejsze implementacje
- HashSet — podstawowa implementacja, wykorzystuje mechanizm hashCode() wbudowany w język Java do organizacji przechowywania
- TreeSet- przechowuje elementy w postaci drzewa, posortowane, gwarantowana złożoność przy wstawianiu elementów (może być to istotne w przypadku dużych zbiorów)
Przykładowy kod
Set<String> zbior = new HashSet<String>();
zbior.add("pierwszy");
zbior.add("drugi");
for (String ciagZnakow : zbior) {
System.out.println(ciagZnakow);
}
java.util.Queue
Kolejki to specyficzna konstrukcja pozwalająca na implementację kolejek typu FIFO (first-in-first-out) i FILO (first-in-last-out, czasem nazywana stosem).
Kolejki udostępniają oczywiście operację dodawania ( add(E element) ) oraz pobierania elementu z głowy bez usuwania go z kolejki ( E peek() ) oraz od razu usuwając go z kolejki ( E remove() ).
Obiekty w kolejce mogą się powtarzać.
Najpopularniejsze implementacje
- ArrayDeque — kolejka oparta o tablice, pozwala na dostęp zarówno od strony głowy ( getFirst() ) jak i ogona ( getLast() ). Elementy przechowywane są w kolejności dodawania
- PriorityQueue — pozwala na przechowywanie i dostęp do elementów wg określonego kryterium (komparatora), sortując elementy wg niego w momencie dodania do kolejki. Przydatna w sytuacjach gdy mamy np. kolejkę obiektów do przetworzenia i chcemy zawsze obsłużyć ważniejsze szybciej niż te mniej ważne
Przykładowy kod
Queue<String> kolejka = new ArrayDeque<String>();
kolejka.add("pierwszy");
kolejka.add("drugi");
System.out.println(kolejka.remove()); //wypisze "pierwszy"
java.util.Map
Mapy, choć nie są formalnie kolekcjami z punktu widzenia języka Java (nie są typu Collection), także służą do przechowywania elementów. Wyróżnia je to, że przechowują nie tyle same elementy, co mapowanie klucz-wartość, tzn. jeden obiekt (klucz) wskazuje na inny obiekt (wartość).
W przypadku map mamy przede wszystkim operację dodawania ( put(K key, V value) ) oraz pobierania elementu ( V get(K key) ).
Obiekty w mapie mogą się powtarzać, jeśli są wartościami. Klucze muszą być unikalne. Wartości i klucze moga być dowolnego typu.
Najpopularniejsze implementacje
- HashMap — mapa, której właściwości są podobne do HashSet’a, kolejność oraz przechowywanie wynikają z implementacji funkcji hashCode()
- TreeMap — elementy są przechowywane w formie posortowanej (wg klucza)
Przykładowy kod
Map<String, Integer> mapa = new HashMap<String, Integer>();
mapa.put("pierwszy", 1);
mapa.put("drugi", 2);
System.out.println(map.get("pierwszy")); //wypisze "1"
Zadanie
Zmodyfikuj program który już napisałaś. Utwórz nową klasę o nazwie KotDAO, posiadającą metodę dodajKoa(Kot kot) oraz pole koty będące kolekcją (listą).
Na początku programu utwórz nowy obiekt KotDAO. Po tym, jak wypełnisz obiekt Kot wywołaj jego metodę dodajKota przekazując jako argument utworzony i wypełniony wcześniej obiekt. Metoda dodajKota powinna dodać kotka przekazanego w argumencie do kolekcji (listy).
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!