Typy złożone – kolekcje

W poprzednich wpisach poznałeś typy proste (int, long, boolean itp), String oraz tablice (jedno i wielowymiarowe). W tym wpisie przyjrzymy się jednym z typów złożonych jakim są kolekcje.

Zacznijmy od tego czym właściwie są kolekcje? Słowo kolekcja oznacza zbiór lub zestaw elementów. Pewnie słyszałeś o kolekcjach obrazów lub znaczków. Zawierają one wiele przedmiotów, często podzielone są one ze względu na styl, okres powstania, wielkość, autora itp. Kolekcje mogą być uporządkowane (bądź nie) według autora, okresu, wieku, nazwy itp.

Więc czym są kolekcje w Java? Podobnie jak w życiu rzeczywistym są to pewne zbiory. Zazwyczaj taki zbiór zawiera elementy tego samego typu. Najprostszą kolekcją którą już poznałeś jest tablica.

Tablice zawierają określoną ilość elementów tego samego typu. Do każdego elementu odwołujesz się za pomocą jego liczby porządkowej, możesz odczytać lub zapisać dowolny element. Niestety nie można w wydajny sposób zmienić rozmiaru tablicy w Java. Żeby powiększyć tablicę należy najpierw utworzyć tablicę większą, potem przepisać wszystkie elementy ze starej tablicy do nowej.

Na szczęście tablice nie są jedynymi dostępnymi kolekcjami w Java. Dziś zapoznamy się wstępnie z podstawowymi kolekcjami dostarczonymi razem z JVM. W kolejnych postach opiszę każdą z kolekcji dokładniej.

W Java wyróżniamy trzy podstawowe typy kolekcji:

  • listy
  • sety
  • mapy

Listy są bardzo podobne do tablic. Zawierają one (jak sama nazwa wskazuje) listę elementów tego samego typu. Każdy element posiada swoją własną liczbę porządkową (podobnie jak w tablicach), z jej użyciem można odczytać daną wartość lub zastąpić ją inną. To co odróżnia listy od tablic to zmienna wielkość. Do list można dodawać nowe elementy w nieskończoność (czytaj, do póki nie braknie Tobie pamięci RAM). Można też wybrane elementy usuwać. Usunięcie elementu nie powoduje powstania “dziury”, tylko przesuwa wszystkie pozostałe elementy o jedno miejsce.

Listę można porównać do książek na półce. Możesz dostawić kolejną książkę na końcu półki, lub włożyć ją pomiędzy inne książki. Każda książka ma swój numer porządkowy. Wyjęcie jednej (usunięcie) spowoduje zmianę “numeracji” pozostałych książek na półce.

Przejdźmy teraz do setów.

Sety można sobie wyobrazić jako worek do którego wkładamy kolejne elementy. Podobnie jak listy nie mają one ograniczonego rozmiaru. Jednak elementy nie posiadają liczy porządkowej. Tak jak w życiu codziennym, włożenie pewnej rzeczy do worka nie gwarantuje, że później ją bezproblemowo wyciągniemy, w setach również nie ma gwarancji kolejności elementów. Przeważnie w “workach” szukamy interesujących nas rzeczy, wyciągamy jedną rzecz sprawdzamy czy to jest ta szukana jak nie wkładamy ją z powrotem i wyciągamy kolejną. Tak samo jest z Set’ami, wkładanie nie stanowi większego problemu, natomiast wyciągnięcie wymaga “przeszukiwania”. Zapamiętaj, używaj Set’ów wtedy kiedy nie interesuje Ciebie kolejność.

Ostatnim popularnym typem kolekcji w Java są mapy. Służą one do przypisania konkretnej wartości do danego klucza. Kluczem oraz wartościami mogą być dowolne typy. Najczęściej spotykanym kluczem jest typ String (zazwyczaj jest to jakiś unikalny identyfikator). Do mapy zawsze wkładamy parę klucz -> wartość. Później możemy zamienić wartość dla danego klucza lub całkowicie usunąć wpis. Przykładem mapy może być lista kodów pocztowych, jeden kod pocztowy odpowiada danemu miastu lub rejonowi np:

Oprócz wymienionych wyżej typów kolekcji czasem spotyka się również:

  • stosy
  • wektory

Są one spotykane bardzo sporadycznie dlatego nie zostały dokładniej opisane.

Przekazywanie parametrów do aplikacji Java

W dzisiejszym świecie aplikacji mobilnych, tworzonych z myślą o iPhone’ach, iPad’ach, Android’ach. Świecie opanowanym przez wszechobecne aplikacje “okienkowe” dla systemów Windows. Pradawna sztuka przekazywania parametrów do programu powoli zanika. Jednakże jest to najprostszy sposób na komunikację pomiędzy programem a użytkownikiem. W tym wpisie przyjrzymy się jak przekazać parametry do naszej aplikacji Java.

Zanim przejdziemy do główniej części wpisu warto chwilę pobawić się w informatyczną archeologie.

Otóż na długo przed powstaniem graficznych interfejsów użytkownika (ang. graphical user interface tzw. GUI) królował tryb tekstowy.

Pierwszym popularnym systemem operacyjnym był DOS “produkowany” przez firmę Microsoft. Obsługiwany był on wyłącznie za pomocą komend tekstowych. Trzeba było znać odpowiednie komendy, żeby poruszać się po systemie plików, edytować plik, czy uruchomić aplikację. Zamiast klikać w ikony symbolizujące katalogi oraz pliki trzeba było znać komendy takie jak cd (skrót od angielskiego change directory, zmień katalog), dir (wypisanie zawartość katalogu).

Rewolucją w ówczesnych czasach był program NortonCommander (uruchamiany komendą nc, prekursor WindowsCommander przechrzczonego później na TotalCommander). Umożliwiał on nawigację po systemie plików za pomocą kursorów oraz przycisku enter. Dopiero potem powstał system Windows 3.11, później Windows 95, 98, 2000/NT, XP, Vista, Windows 7, 8, 10 … i tak dalej.

W tych pradawnych czasach nie było myszki, a programowi trzeba było w jakiś sposób powiedzieć co ma zrobić. Więc ktoś wpadł na pomysł żeby wszystko co znajduje się za nazwą programu nazwać “argumentem” (parametrem, przełącznikiem; pojęcia te używane są zamiennie), przekazać to do programu i na tej podstawie sterować jego wykonaniem (tj. zmieniać zachowanie).

Żeby wejść do katalogu ‘dokumenty’ w systemie DOS wydawało się komendę ‘cd dokumenty’. Tutaj właśnie słowo ‘dokumenty’ jest parametrem i mówi programowi cd co ma zrobić (zmienić aktualny katalog na ‘dokumenty’, o ile taki istnieje).

Po tym krótkim wstępie przejdźmy do obsługi argumentów w Java.

Zacznijmy od przyjrzenia się metodzie main. Pewnie pamiętasz, że w jej definicji zawsze występował fragment: String[] args. Z poprzedniej serii wpisów o tablicach powinieneś już wiedzieć, że ten zapis oznacza tablicę typu String dostępną pod zmienną args.

Co znajduje się w tej tablicy? Otóż, właśnie tam znajdują się argumenty (ang. arguments, stąd skrót ‘args’) przekazane do programu. Każdy element tablicy zawiera jeden argument.

Przyjrzyjmy się teraz prostemu programowi który wypisze nam wszystkie przekazane parametry:

Oczywiście przed uruchomieniem program należy skompilować:

Teraz już wiesz, że ListArguemtns.java jest parametrem przekazanym do programu javac.

Po kompilacji możemy uruchomić nasz program:

W rezultacie na ekranie zobaczymy tekst: Argumenty przekazane do programu. Nie zostanie wyświetlone nic więcej, bo nie przekazaliśmy żadnego argumentu do programu. Przyjrzyjmy się teraz innemu uruchomieniu:

Tym razem wynik programu będzie bardziej obszerny:

Jak widzisz każdy kolejny argument został wypisany w nowej linii. Pierwszy argument dostępny jest na pozycji zero (0) w tablicy args. Pewnie zauważyłeś iż kolejne argumenty oddzielone są odstępami (spacjami), więc jak przekazać argument ze spacją? Odpowiedź znajduje się w ostatnim argumencie przekazanym do poprzedniego wywołania programu: kolejny argument. Jest on ujęty w znaki pojedynczego cudzysłowu (‘) żeby zgrupować razem oba wyrazy, można również w tym celu użyć podwójny cudzysłów (“), efekt będzie taki sam.

Więcej informacji, przykładów oraz zadań (wraz z rozwiązaniami) wysyłam mailem. Zapisz się na listę żeby je otrzymać.

Wykorzystanie tablic w Java

Po zapoznaniu się z tablicami w Java oraz przyjrzeniu się bliżej tablicom jedno- oraz wielowymiarowym, czas przejść do praktycznego ich wykorzystania. W poprzednich wpisach często odwoływałem się do dziennika ocen więc zaimplementujmy teraz taki banalny dziennik.

Przykład ten będzie w całości statyczny tj. nie będzie można do niego dynamicznie wpisać danych. Wszystkie dane będą na stałe zapisane w programie.

Zanim przejdziemy do samego kodu, zastanówmy się co będzie nam potrzebne. Na pewno będziemy musieli przechować gdzieś listę uczniów w klasie oraz ich oceny. Oczywiście do tego celo użyjemy tablic:

Ponieważ cały program będzie statyczny oceny będą wyliczane z wykorzystaniem operacji modulo (reszty z dzielenia) oraz aktualnej liczby wpisanych ocen.

Oto kod naszego programu symulującego dziennik ocen:

Wspomniane wcześniej dwie tablice zdeklarowane są w liniach cztery oraz dziesięć.

Pierwsza z nich students przechowuje elementy typu String. Jest to nasza jednowymiarowa tablica uczniów. Każdy uczeń przypisany jest do jednego numeru, tak jak to jest w prawdziwym dzienniku szkolnym. Jedyną drobną różnicą jest to że nasza numeracja rozpoczyna się od zera.

Tablica scores pełniąca w naszym programie funkcję kartki z ocenami zdeklarowana jest jako dwuwymiarowa tablica zmiennych typu int. Efektem ubocznym takiej deklaracji jest to, że nasz dziennik nie będzie akceptował ocen połówkowych. Jakie są rozmiary tej tablicy? Otóż definiowane są one przez dwie zmienne: studentsCount oraz scoresCount. Takie podejście pozwala nam nie martwić się o zmianę rozmiaru tej tablicy w momencie dodania bądź usunięcia ucznia z klasy oraz dodania nowej kolumny z ocenami. Zmienna scoresCount inicjalizowana jest w linii 3, odpowiada ona za ilość ocen przypisanych do ucznia.

Interesująca jest natomiast zmienna studentsCount gdyż zawiera ona wartość students.length. Ale co to oznacza? Otóż każda tablica w Java posiada właściwość length (ang. długość), odpowiadającą rozmiarowi tej tablicy. W naszym przypadku studentsCount będzie zawierało wartość 4 gdyż tablica students została zdeklarowana jako cztero-elementowa (gdyż w liniach cztery do siedem podaliśmy cztery imiona oraz nazwiska uczniów). Jeżeli dopiszesz lub usuniesz ucznia z tej tablicy program po przekompilowaniu oraz ponownym uruchomieniu wypisze nowy zestaw ocen. Nie potrzebne są dalsze zmiany w kodzie programu, gdyż wszędzie odwołujemy się do zmiennej studentsCount, a nie do wpisanej na sztywno wartości.

Dobrze, przejdźmy teraz do pętli odpowiedzialnych za wypełnienie tablicy scores. W tym celu wykorzystamy dwie zagnieżdzone pętle for w liniach trzynaście oraz osiemnaście.

Pierwsza z nich iteruje po zmiennej i odpowiedzialnej za pierwszy wymiar. W środku niej znajduje się kolejna pętla, iterująca tym razem po zmiennej j w której to wyliczamy ocenę ucznia oraz zwiększamy o jeden naszą podstawę oceny (zmienną scoreBase).

Po co jest nam scoreBase? W celu uproszczenia programu, nasz dziennik nie jest do końca sprawiedliwy, gdyż oceny ucznia zależą od pozycji na której się znajduje w dzienniku. Dokładniej mówiąc każda ocena jest resztą z dzielenia (modulo) sumy poprzednio wpisanych ocen (powiększonej o trzy) czyli zmiennej scoreBase. Obliczenia te dokonywane są w linii piętnastej; w tej samej linii następuje również przypisanie wyniku do odpowiedniego miejsca w tablicy scores

Dzięki temu że zmienna i ograniczona jest do wartości studentsCount oraz zmienna j ograniczona jest do wartości scoresCount nasz program poradzi sobie bez problemu ze zmianą ilości uczniów oraz ocen; tzn. każdy nowo dodany uczeń otrzyma wyliczony zestaw ocen.

Została nam do omówienia ostatnia część programu: wypisanie zawartości naszego dziennika. Kod odpowiedzialny za to zadanie rozpoczyna się w linii dwudziestej, gdzie wypisujemy nagłówek Oceny:.

Następnie znajduje się pierwsza pętla iterująca po zmiennej i do momentu w którym warunek i > studentsCount będzie fałszywy. Następnie wypisujemy numer porządkowych ucznia oraz jego nazwisko (linia dwudziesta druga). Zauważ, że wypisujemy wartość i + 1 w celu uzyskania numeracji od jeden, a nie od zera. W tej samej linii odczytujemy również imię oraz nazwisko ucznia z tablicy students.

Zanim przejdziemy do kolejnej pętli for przyjrzyjmy się linii dwudziestej trzeciej. Zwróć uwagę, że używamy tutaj instrukcji print, a nie println. Oczywiście jest to celowe zachowanie, nie chcemy przejść do nowej linii, chcemy tylko przesunąć kursor o dwa taby. Wypisanie znaku specjalnego \t powoduje przesunięcie kursora o 8 pozycji w prawo, używa się tego znaku po to żeby nie wpisywać na sztywno osiem spacji w kodzie programu. Warto tutaj wspomnieć ze wielkość “taba” może być zmieniona przez użytkownika.

Ostatnią pętlą for jest pętla wypisująca oceny danego ucznia (linia dwadzieścia cztery). Iteruje ona po zmiennej j do póki nie będzie ona równa wartości scoreCount. Umożliwia to wypisanie wszystkich ocen danego ucznia. W jej wnętrzu znajduje się tylko jedna instrukcja System.out.print która wypisze ocenę z pozycji j ucznia i poprzedzoną spacją.

Na koniec zewnętrznej pętli (tej iterującej po i) wypisujemy znak zakończenia linii korzystając z instrukcji System.out.println() beż żadnego parametru. Oczywiście powoduje to przeniesienie kursora do nowej linii.

Oto wynik działania naszego programu:

Jeżeli coś jest nie jasne, zapraszam do skomentowania tego postu. Dzięki temu będę mógł Tobie lepiej wyjaśnić zagadnienia poruszone w tym wpisie.

Kolejny wpis poświęcony będzie przekazywaniu parametrów do programu.

Tablice wielowymiarowe

W poprzednim wpisie zapoznaliśmy się z tablicami w Java. Dla uproszczenia zaczęliśmy od tablic jednowymiarowych w których to na danej pozycji przechowana jest tylko jedna wartość. Analogią takiej tablicy wykorzystaną w poprzednim wpisie jest lista obecności w dzienniku szkolnym, gdzie jednemu numerowi odpowiada jedna osoba. Taką jednowymiarową tablicę deklarujemy w następujący sposób:

Natomiast dostajemy się do danego elementu w następujący sposób:

Zauważ, że pierwszy element posiada numer zero. To tyle powtórzenia, czas na nowe rzeczy!

Więc co to jest ta tablica wielowymiarowa? Korzystając dalej z naszej analogii dziennika szkolnego, tablica wielowymiarowa odpowiada stornie z ocenami z danego przedmiotu. Na takiej stronie do jednego numeru przypisana jest lista ocen danej osoby z tego przedmiotu. Inaczej mówiąc na jednej pozycji przechowywanych jest wiele wartości.

Taka strona z ocenami jest przykładem tablicy dwuwymiarowej. Jest to zagnieżdżenie tablicy w tablicy. Zdeklarujmy tera taką tablicę dwuwymiarową:

Na pierwszy rzut oka możesz zauważyć drugą parę nawiasów kwadratowych za int[], właśnie ta dodatkowa prawa nawiasów dodaje dodatkowy wymiar do tablicy. Zapis int[][][] oznacza tablice trójwymiarową, natomiast int[][][][] czterowymiarową… i tak dalej. Zazwyczaj w programach Java spotyka się tablice jedno- bądź dwuwymiarowe.

Drugą rzeczą godną zauważenia jest to że wymiary nie muszą być jednakowe. Jak widzisz na powyższym przykładzie pierwszy wymiar posiada cztery elementy, drugi pięć natomiast trzeci ma tylko trzy elementy.

Żeby dostać się do dowolnego elementu tablicy wielowymiarowej należy podać pozycje w każdym wymiarze, tj. żeby dostać się do elementu o wartości zero w naszej tablicy scores należy napisać scores[1][2]. Dlaczego tak? Gdyż zero znajduje się na pozycji trzeciej w drugim wymiarze, pamiętając, że numeracja zaczyna się od zera, należy odjąć jeden od każdej wartości.

Jeszcze kilka słów o deklaracji tablic wielowymiarowych. Otóż przy deklaracji najważniejszy jest rozmiar pierwszego wymiaru tj. rozmiar pierwszej tablicy, najważniejszy jest on dlatego, że nie może on zostać potem zmieniony. Jeżeli w momencie deklaracji znasz rozmiary wszystkich wymiarów np.:

zadeklaruje nam tablicę ciągów znakowych (tekstów) o wymiarze dwa na dwa. Jeżeli nie znasz rozmiarów kolejnych wymiarów lub rozmiary w danym wymiarze nie są jednakowe, nie ma problemu możesz potem ręcznie zadeklarować każdy element tablicy wielowymiarowej:

Powyższy fragment kodu zadeklaruje nam tablicę dwuwymiarową pod nazwą example2, elementami tablicy są zmienne typu String. Następnie w linii drugiej przypisujemy do pierwszego elementu tej tablicy tablicę o rozmiarze dwa, natomiast w linii trzeciej do drugiego elementu przypisujemy tablicę o rozmiarze trzy. W rezultacie otrzymujemy zmienną o dwóch wymiarach, pierwszy z nich ma dwa elementy, natomiast w drugim wymiar nie ma stałego rozmiaru.

To tyle jeżeli chodzi o tablice wielowymiarowe. Jeżeli masz jakieś pytania lub wątpliwości zachęcam do umieszczenia ich w komentarzach pod tym wpisem.

W kolejnym wpisie zajmiemy się praktycznym zastosowaniem tablic wielowymiarowych.

Używanie tablic w Java

W poprzednim wpisie zapoznaliśmy się wstępnie z teorią zmiennych tablicowych w Java. Jeżeli nie wiesz czym są tablice w Java, warto żebyś tam spojrzał.

Dziś zajmiemy się wykorzystaniem zmiennych tablicowych. Najpierw warto przypomnieć jak wygląda deklaracja tablicy w Java.

Powyższy fragment kodu deklaruje zmienną o nazwie names typu String. Warto tutaj od razu zauważyć, że nazwa tej zmiennej jest rzeczownikiem w liczbie mnogiej; otóż wszystkie zmienne tablicowe w Java zgodnie z konwencją zwykło się właśnie nazywać w liczbie mnogiej. Nasza tablic names, została od razu zainicjalizowana trzema elementami:

  1. ala
  2. magda
  3. kasia

Elementy te będą dostępne w kolejnych komórkach tablicy pod numerami: 0, 1, 2. Ponieważ nasza tablica została od razu zainicjalizowana wartościami nie musimy podawać jej rozmiaru (ilości elementów które pomieści). W takim wypadku JVM sam policzy liczbę elementów i ustawi rozmiar tablicy na trzy.

W przypadku kiedy nie znamy elementów tablicy musimy posłużyć się trochę innym zapisem:

W tym wypadku zmienna users będzie zawierać pustą dziewięcioelementową tablicę. Elementy tej tablicy mogą zostać przypisane później ale o tym za chwilę.

Przyjrzyjmy się jeszcze jednemu przykładowi:

Cóż to jest za dziwny stwór? Otóż zmienna examples jest pustą tablicą, nie zawierającą żadnych elementów, nie można też do niej nic przypisać gdyż zawiera ona zero miejsc na zapisanie danych. Zmienna ta jest praktycznie bezużyteczna.

Dobrze, to jak przypisać wartość do danego elementu tablicy? Bardzo prosto, spójrz na poniższy przykład:

Obie zmienne names będą takie same. Z tym, że w tym przykładzie dokładnie widzimy jak przypisać wartość do danego miejsca w tablicy. Wykorzystywane do tego celu ponownie są nawiasy kwadratowe ([]), tym razem w ich wnętrzu podajemy numer pod którym chcemy zapisać daną wartość. UWAGA! w tym momencie liczymy od ZERA, a nie od jedynki (tak jak to widać na powyższym przykładzie), natomiast podając rozmiar tablicy (new String[3];) liczymy od JEDNEGO.

Oczywiście do jednej pozycji w tablicy można przypisać wartość wielokrotnie. W takim wypadku ostatnia przypisana wartość jest zapamiętywana, np.:

W takim wypadku ostatnim elementem tablicy names będzie wartość zosia.

Jak to sprawdzić? Trzeba by odczytać co znajduje się na danym miejscu w tablicy. Jak to zrobić? Tak samo jak z zapisywaniem:

Na ekranie wyświetli się: zosia. Takie to proste :)

Rzadko kiedy będziesz w sytuacji w której będziesz dokładnie znał rozmiar tablicy. Zazwyczaj jest tak że jej rozmiar ustalany jest dynamicznie w programie. Jak to się robi?

W tym momencie zmienna values będzie zawierała pięcioelementową tablicę. Jak widzisz rozmiar tablicy może pochodzić z innej zmiennej, może również być obliczony w trakcie działania programu np. na podstawie danych otrzymanych od użytkownika.

Skoro tablica może być dowolnych rozmiarów to jak pobrać drugi element od końca? Do takich zadań służy specjalna właściwość tablicy zwana length, zawiera ona informację o rozmiarze właśnie tej tablicy. Więc problem wyświetlenia drugiego elementu od końca jest prosty do rozwiązania:

Jak zatem wypisać wszystkie elementy z tablicy? Wystarczy do tego wykorzystać length oraz pętlę for:

W bardzo podobny sposób można wypisać również wszystkie elementy tablicy:

To tyle na dziś! Jeżeli coś jest nie jasne, bądź czegoś nie rozumiesz napisz mi o tym w komentarzach!