Twitter: @grzegg
Kategoria: java, Tagi: - - - .

Java [32] – Kolekcje i mapy

Tym razem omówimy dwa podstawowe struktury danych w Javie: kolekcje i mapy. Kolekcje służą do gromadzenia obiektów natomiast mapy pozwalają na określenie pewnych relacji miedzy nimi. Obie są częścią frameworku, czyli „platformy programistycznej” o nazwie Collections ale kolekcje implementują interfejs Collections, natomiast mapy nie.

Kolekcje

Jak sama nazwa sugeruje framework Collections służy do tworzenia „kolekcji” czyli struktur umożliwiających grupowanie obiektów i ułatwiający pracę z nimi. Dotychczas kiedy chcieliśmy wspólnie operować jakąś grupą obiektów używaliśmy w tym celu tablic lub tworzyliśmy obiekty o odpowiednich polach. Jak spróbuję pokazać, wykorzystanie klas oferowanych przez Collections pozwala w wielu przypadkach ułatwić pracę z grupami obiektów, choć nie oznacza to, że należy w ogóle zapomnieć o tablicach.

Zanim przejdę do krótkiego omówienia wybranych klas kolekcji przyjrzyjmy się jednej z nich, klasie ArrayList.

Rzut oka na ArrayList

Na pytanie czym jest ArrayList można by odpowiedzieć, że jest to lista, a bardziej kolokwialnie, że to jednowymiarowa tablica na sterydach. Najlepiej zobaczyć parę możliwości takiej struktury przykładzie:

Po uruchomieniu wynik powinien przypominać ten, choć wylosowane liczby będą inne:


Lista posiada 10 elementów
Zawartość początkowa : [4, 2, 6, 1, 6, 5, 1, 7, 0, 5]
Zawartość po dodaniu : [4, 2, 8, 6, 1, 6, 5, 1, 7, 0, 5]
Zawartość po wstawieniu : [4, 2, 9, 6, 1, 6, 5, 1, 7, 0, 5]
Zawartość po usunięciu : [2, 9, 6, 1, 6, 5, 1, 7, 0, 5]
Pierwsza liczba 9 jest pod indeksem: 1
Zawartość po usunięciu 9: [2, 6, 1, 6, 5, 1, 7, 0, 5]
Piąty element: 5
dodatkowe: [7, 6]
dane: [2, 6, 1, 6, 5, 1, 7, 0, 5, 7, 6]
tablica: class [Ljava.lang.Integer;, 1 element: 2
Po czyszczeniu: []

Zapewne najbardziej rzucającą się w oczy różnicą między tablicami i ArrayList jest „rozciągliwość” tej drugiej. Nie musimy z góry ustalać jej rozmiaru, po prostu w miarę potrzeb dodajemy i usuwamy z niej elementy bez zbędnych ceregieli. Inną różnicą jest to, że odwołujemy się do poszczególnych elementów za pomocą metod, do których przekazujemy indeks, jeśli jest taka potrzeba.

Zwróć też uwagę na sposób w jaki utworzyliśmy listę:


ArrayList dane = new ArrayList();

W celu zdefiniowania typu przechowywanych w tablicy obiektów użyliśmy formy znanej z poprzedniej lekcji, w której omawialiśmy typy generyczne.

Trochę o frameworku Collections

Teraz trochę teorii. Framework Collections, jak wspomniałem powyżej dostarcza wiele klas umożliwiających tworzenie różnych struktur służących przechowywaniu obiektów. Wszystkie mają pewne cechy wspólne a także każda z nich swoiste, dzięki czemu można wybrać taką, która będzie optymalna dla konkretnego zadania. Tutaj omówię zaledwie niektóre z nich, ale zawsze można zajrzeć do szerszych opracowań, jak np. przewodnik po frameworku Collections Oracle.

Kolekcje mogą przechowywać wyłącznie odwołania, a więc możemy ich użyć do typów obiektowych. Chcąc przechowywać liczby czy inne typy prymitywne, należy użyć wrappingu. Na szczęście działa tu autoboxing i unboxing, dzięki czemu nie musimy ręcznie przekształcać typów prymitywnych na obiektowe. Pewnym wyjątkiem w powyższym kodzie było ręczne utworzenie obiektu typu Integer, kiedy chcieliśmy usunąć konkretną liczbę. Gdybym umieścił argument 9 w wywołaniu metody, zostałaby ona zinterpretowana jako indeks komórki.

Wybrane Interfejsy frameworku Collections

Częścią frameworku Collections są dostarczane przez niego podstawowe interfejsy: Collection, Deque, List, NavigableSet, Queue, Set, SortedSet oraz kilka dodatkowych, jak Comparator, Spliteator, Iterator czy RandomAccess. Implementacja tych interfejsów przez różne metody sprawia, że ich „obsługa” ma wiele cech wspólnych, a więc jest łatwiejsza, choć trzeba pamiętać, że klasy mogą różnie implementować poszczególne interfejsy. Wszystkie klasy kolekcji implementują także interfejs Iterable, co umożliwia np. użycie pętli typu foreach.

Chyba najbardziej podstawowym interfejsem z wymienionych jest oczywiście interfejs Collections. Jest on rdzeniem wszystkich kolekcji, deklaruje więc zestaw metod, które wszystkie kolekcje powinny implementować, należą do nich na przykład: add(), clear(), contains(), remove() czy size().

Kolejne interfejsy „doprecyzowują” rodzaj kolekcji. Interfejs List deklaruje metody charakterystyczne dla kolekcji takich jak ArrayList przechowujących uporządkowaną sekwencję obiektów. Z kolei Set opisuje metody dla kolekcji, które nie pozwalają na przechowywanie zduplikowanych elementów. SortedSet oraz NavigableSet jak można się domyślić, stanowią rozszerzenia Set. Metody zadeklarowane w Queue i Deque są używane przez kolekcje w formie „kolejki”, porządkując je w kolejności w jakiej będą użyte.

Wybrane klasy frameworku Collections

Krótko opisane powyżej interfejsy wskazują jakich podstawowych rodzajów kolekcji możemy używać wykorzystując framework Collections, przy czym trzeba pamiętać, że poszczególne klasy mogą implementować wiele interfejsów jednocześnie. Dwa zasadnicze typy kolekcji to List oraz Set. Jak można wywnioskować z opisu interfejsów, które implementują, pierwszy typ pozwala gromadzić uporządkowane grupy obiektów, które mogą się powtarzać a drugie nie pozwalają na duplikaty. Przyjrzyjmy się teraz krótko niektórym klasom właściwym dla typów kolekcji:

ArrayList

Implementuje interfejs List, umożliwia przechowywanie sekwencji obiektów. Przykład użycia podałem powyżej.

LinkedList

Oprócz interfejsu List implementuje Queue i Deque, dzięki czemu korzysta m. in. z takich metod jak addFirst(), getFirst(), removeFirst() i analogicznie: addLast(), getLast(), removeLast(), które jak można wywnioskować z nazw ułatwiają pracę na obu końcach listy.

HashSet

Jak sama nazwa wskazuje implementuje interfejs Set, z kolei pozostała część nazwy informuje, że mamy do czynienia z „tablicą z haszowaniem” („tablicą mieszającą”) co nie wnikając w szczegóły ma takie znaczenie, że elementy w tablicy są porządkowane wg. specjalnych algorytmów tak aby dostęp do nich był jak najszybszy. W efekcie kolejność elementów w kolekcji nie musi odpowiadać kolejności w jakim je dodawaliśmy. Na przykład po wykonaniu takiego kodu:

otrzymałem:


Aminokwasy :[Ile, Ala, Arg, Cys]

TreeSet

Ten typ kolekcji przechowuje dane tak aby dostęp do nich był możliwie szybki. Związane z tym jest sortowanie elementów a także przechowywanie ich w drzewie (niestety nie będę tu wyjaśniał co to dokładnie oznacza).

Przykład:

Wynik:


Aminokwasy :[Ala, Arg, Cys, Ile]

Mapy

Mapy to struktury służące do przechowywania danych oparte na powiązaniu: kluczwartość. Kluczem może być np. numer PESEL, wartością imię i nazwisko przechowywane w jednym string-u. Klucze muszą być unikalne, wartości mogą się powtarzać. Mapy są w pewnym stopniu podobne do opisanych powyżej kolekcji, ale w przeciwieństwie do nich nie implementują interfejsu Iterable, zatem nie możemy na nich stosować, przynajmniej bezpośrednio, pętli typu foreach, ponadto służą nie tylko gromadzeniu obiektów ale także pozwalają na zachowanie relacji między nimi.

Podobnie jak w przypadku kolekcji mapy implementują charakterystyczne dla nich interfejsy: Map, Map.Entry, NavigableMap, SortedMap. Najbardziej podstawowym z nich jest oczywiście Map, który deklaruje m. in. takie podstawowe metody jak put(), get() czy remove().

Klasy implementujące wspomniane interfejsy to: HashMap, EnumMap, TreeMap, WeakHashMap, LinkedHashMap oraz IdentityHashMap. Ponadto istnieje AbstractMap, która jest „szkieletem implementacji” interfejsu Map i jest klasą nadrzędną wobec poprzednio wymienionych.

Ogólny sposób działania map pokażę na przykładzie HashMap:

Działanie podstawowych metod i technik opisane jest w komentarzach kodu.

Leave a Reply