#05 – Kolekcje w Javie

By 11 września 2014Kurs Javy
5

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"
Schemat ideowy - lista

Schemat ideowy – lista

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 liście 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
  • TreeList – 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);
}
Schemat ideowy - zbiór

Schemat ideowy – zbiór

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"
Schemat ideowy - kolejka

Schemat ideowy – kolejka

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"
Schemat ideowy - mapa

Schemat ideowy – mapa

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).

zip Pobierz rozwiązanie tego zadania

5

Licencja Creative Commons

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!

  • 9
  •  
  •  
  • 5
  •  
  • Aga

    Czy jest szansa, że pojawi się rozwiązanie tego i kolejnego z lekcji 6 rozwiązania do tych zadań? Nie bardzo rozumiem co z tym obiektem kotek…

    • Aga prosimy o cierpliwość, postaramy się opublikować je w ciągu tygodnia :) Jak tylko je umieścimy, to damy znać w komentarzu.

    • Cześć, odpowiedzi już zaczęły się pojawiać :) Do lekcji 6 włącznie są już dodane, do kolejnych lekcji będziemy je dodawać wkrótce. Dzięki za wyrozumiałość!

      • Aga

        Dzięki bardzo! No właśnie chodziło mi o to, że w treści zadania parametrem metody jest kotek typu Kotek a mieliśmy klasę Kot i zastanawiałam się jak to mam zrobić – po prostu błąd w treści, już ogarnęłam :)

        • Przepraszamy, nasz błąd w redagowaniu treści :( już poprawiłem, aby na przyszłość nie wprowadzał w błąd.

          Dzięki wielkie za zwrócenie uwagi! :)

  • 1. Czemu static KotDAO kotDao = new KotDAO(); pojawił się:
    1a) jako static?
    1b ) i nie w main(), tylko jako instance variable?

    2. W kodzie jest try/catch przy inputowaniu daty, ale rozumiem, że on już jest opcjonalny jak mamy dla daty regex?

    • 1) Pole typu KotDAO jest statyczne ponieważ póki co część kodu, który je wykorzystuje, jest napisany w metodzie main, która także jest statyczna. Ale przede wszystkim nie jest to pole obiektu (instance variable)! Jest ono wydzielone ponownie – dla przejrzystości. W przyszłości etn kod będzie ewoluować w normalną aplikację, możemy więc zacząć wprowadzać elementy właściwego jego podziału i wydzielania elementów.
      2) Tak, jeśli jesteśmy pewni prawidłowego działania naszego regex’a, możemy pominąć try-catch.

      • // Ale przede wszystkim nie jest to pole obiektu (instance variable)!

        Zaćmiło mnie…

  • kago

    Czy na pewno autorowi chodziło o TreeList, a nie przypadkiem o TreeSet? Ten pierwszy z pakietów Apache nie do końca uznał był za tzw. core’ową funkcjonalność java i nie koniecznie element JCF.

    • Dzięki za zwrócenie uwagi, poprawiliśmy. Oczywiście implementacja TreeList nie jest dostępna bez importu bibliotek.

  • Andrzej Karczewski

    Jaka jest różnica pomiędzy wytworzeniem obiektu KotDAO i użyciem jego metod, a określeniem metod jako statyczne i użyciem ich bez wytwarzania obiektu KotDAO? Kiedy powinno się stosować które podejście? Dziękuję i pozdrawiam.

    • Metody statyczne powinieneś traktować jako specjalne metody, które stosujesz dla metod pomocniczych dla twojej aplikacji np. zmiana kodowania tekstu, konwertowanie obiektów itp.. Standardowo powinieneś stosować obiekty, ponieważ, dzięki temu można korzystać ze wszystkich dobrodziejstw Springa, łatwiej też testować tak napisaną aplikacje. W przyszłości łatwiej też będzie podmienić implementację na inną.

  • xt

    „FILO (first-in-last-out, czasem nazywana stosem)” – hmm… ciekawe…

    • Hmm, zdaje nam się jednak, że jest to poprawne określenie ;)

  • Kasia

    W przykładowym kodzie w java.util.Set, gdy używam „zbior.iterator()” to wyskakuje mi błąd „can only iterate over an array or an instance of java.lang.Iterable”.
    Jak mam sam „zbior” jest OK. Czy to znaczy, że w zbiorach nie można
    używać tej konstrukcji? Tylko np. w liście, gdzie jest ustalona
    kolejność elementów?
    PS. Bardzo fajny blog :)

    • Cześć, dziękujemy faktycznie wkradł się błąd. Aby iterować po secie nie trzeba wywoływać na nim metody iterator().

      • Kasia

        Dziękuję za odpowiedź :-)

  • Uczący się Javy

    Mały błąd (przynajmniej nieakceptoiwalny dla Java 8). Zamiast:
    Queue kolejka = new ArrayDequeue();

    Powinno być:
    ArrayDeque kolejka = new ArrayDeque();

    Reszta wygląda na ok ;)

    • Cześć,
      wydaje mi się, że błąd mógł dotyczyć czego innego – np. braku importów? ArrayDeque implementuje interfejs Queue (https://docs.oracle.com/javase/8/docs/api/java/util/ArrayDeque.html), więc zapis:
      Queue kolejka = new ArrayDequeue();
      jest jak najbardziej prawidłowy :) Czy możesz podesłać nam kod, który nie działał? Być może będziemy w stanie pomóc :)

      • Uczący się Javy

        Już wiem gdzie jest błąd, że mi nie chciało z ‚Queue’ przejść. W konstruktorze obiektu kolejki jest błąd. Zamiast new ‚ArrayDequeue()’ powinno być ‚new ArrayDeque()’. W takim wypadku przechodzi i działa jak powinno.

        • Faktycznie, przepraszamy najmocniej – czytałem ten kod i nie zauważyłem, gratuluje spostrzegawczości i dzięki za zwrócenie uwagi! Już poprawiamy w listingu

  • olekxd

    Hej jedno pytanie w kursie pisze ze „ArrayList – … a operacje odczytu nie są w pętli for-each”. Nie do konca rozumiem dlaczego operacje odczytu nie sa w petli for-each skoro jest to zbior Stringow ktore mozna ta petla kolejno odczytac, czy jest to sposob niezaleny ?

    • Chodzi o to, że jeśli odczyt danej listy następuje tylko w pętli for-each, można rozważyć użycie LinkedList, ale ArrayList także sprawdzi się bardzo dobrze w tym wypadku.
      Jeśli chodzi o daty to w API Javy przed 1.8 nie było typu, który przechowywałby samą datę (typ Date zawsze przechowuje także godzinę). Od Javy 8 jest to możliwe – zerknij np. na nasz wpis http://kobietydokodu.pl/niezbednik-juniora-obsluga-dat-i-czasu/, w którym opisujemy np. LocalDate – idealną do zastosowania w tym przypadku.

  • Mariusz

    A faceci nie mogą korzystać z tej, krwistej strony :)

    • Jasne że mogą – strona jest adresowana do każdego, kto jest zainteresowany nauką Javy

  • Bsh

    Witam, dlaczego piszemy np. List lista = new LinkedList(); a nie LinkedList lista = new LinkedList(); Nie mamy przecież wtedy dostępu do wszystkich metod LinkedList

    • Dokładnie z tego powodu – dzięki temu masz pewność, że cały kod który napiszesz będzie działał w oparciu o dowolną listę, a nie tylko LinkedListę. Jeśli w przyszłości z jakiegoś powodu będziesz musiał zmienić implementację, używanie deklaracji List od początku może zaoszczędzić Ci sporo pracy.