Od zera do aplikacji developera – Tablice i listy [#04]

simplecoding.pl 7 lat temu

Cześć, dzisiaj kolejna część kursu dla osób zaczynających przygodę z programowaniem. Ty masz już o tym coraz większe pojęcie! Nie można jednak poprzestać na tym. Dzisiaj pokażę Ci bardziej rozbudowane struktury danych. Będą to tablice i listy.

Tablice

Zaczniemy od tablic. Tablice to nic innego jak kontener na dane. W różnych językach tablice są inaczej zaimplementowane i używane. W Javie nasz “kontener” przechowuje dane o określonym typie, które musimy zadeklarować. Przykładowo:

W przykładzie zadeklarowana tutaj jest tablica “intów” (liczb całkowitych) o rozmiarze 5. Tak, dana tablica ma stały rozmiar. Trzeba go określić przy inicjalizacji. W tym przypadku tablica jest pusta, żadne dane nie zostały dodane. Możemy to jednak zrobić przy inicjalizacji. Wtedy nie musimy podawać rozmiaru tablicy. Jest on ustalany na podstawie ilości elementów w tablicy.

Myślę, iż różnice są widoczne.. W nawiasach kwadratowych nie podałem rozmiaru, natomiast potem w “wąsach” są elementy, które będą należeć do tablicy. Rozmiar tej tablicy to 4. Nie kłamię. Możesz się o tym przekonać odwołując się do pola tablicy, w którym ustalona jest jej wielkość – length

Mógłbyś sobie pomyśleć, iż skoro mamy dostęp do tej zmiennej to możemy zwiększyć rozmiar tablicy, na przykład tak.

Cóż… nie. Jest to zmienna finalna, więc przy próbie zmiany wartości zmiennej finalnej Twój kod się nie skompiluje.

Elementy tablicy

Dobrze, jest tablica, są elementy, co dalej? Sam rozmiar tablicy to jeszcze zbyt mało by móc coś sensownego z nią zrobić. zwykle chcemy odwołać się do pojedynczego elementu. Jest to bardzo proste. Dodajemy do nazwy zmiennej numer elementu do którego chcemy się odwołać w tablicy. Ostatnio już o tym mówiłem, ale często się o tym zapomina. Numeracja zaczyna się od 0, nie od 1! Tak więc dla tablicy, która ma 4 elementy możemy odwołać się do numerów 0, 1, 2 oraz 3.

Co się stanie, gdy odwołamy się do numeru, którego nie ma? Przykładowo, o ile będziemy chcieli wypisać element o numerze 99 dla tablicy 4 elementowej, Java “wypluje” wyjątek:

Sytuacja będzie miała również miejsce, gdy podany numer jest za mały, a więc dowolna liczba ujemna (elementy w tablicy zawsze zaczynają się od zera).

Konkretne elementy w tablicy można łatwo nadpisać. Odwołujemy się do nich tak, jak wcześniej i robimy zwykłe przypisanie:

Tablice wielowymiarowe

Czasami dochodzi do sytuacji, iż nie chcemy mieć tablicy jednowymiarowej i potrzebujemy przechowywać “kontener w kontenerze”. Nie ma z tym problemu. Koncepcje, inicjalizacja i odwoływanie się do elementów odbywa się w analogiczny sposób. Tak wygląda tablica tablic, która posiada 5 tablic, a każda z nich ma rozmiar 4:

Tablice w tablicy mogą mieć jednak różny rozmiar i możemy to również ustalić na początku, na przykład poprzez przypisanie tablic. Tutaj na przykład mamy tablicę, która posiada dwie tablice. Jedna ma rozmiar 2, a druga 3:

Odwołanie się do elementów oraz ich nadpisywanie odbywa się w analogiczny sposób jak dla tablic jednowymiarowych:

Listy – kiedy tablice to za mało

Tablice w Javie są użyteczne gdy chcemy przechowywać określoną ilość elementów, która jest NIEZMIENNA. Stety niestety, taka sytuacja występuje rzadko. zwykle takimi kontenerami manipulujemy, dokładamy elementy, usuwamy… W wielu językach programowania istnieje inna struktura danych, która nazywa się listą. W Javie jest to interfejs (bardzo ogólnikowo interfejs to opis danych/funkcji, jakie musi spełnić implementująca go klasa, o tym jednak więcej niebawem), który domyślnie ma dwie implementacje.

Różnica między tablicami i listami jest przede wszystkim taka, iż listy mogą mieć zmienną ilość elementów. Druga dość ważna różnica to sposób, w jaki używamy list. Przykładowo, nie bierzemy kolejnego elementu przez podanie kolejnego numeru w nawiasach kwadratowych ale poprzez odpowiednią metodę – get(nr).

Dwie implementacje listy

Jak już wspomniałem, w Javie mamy dwie implementacje listy. Są to:

  • LinkedList – lista wiązana
  • ArrayList – lista tablicowa

W prostych przypadkach, takie jak tutaj, konkretna implementacja listy nie odgrywa dla nas roli. Jednak z biegiem czasu, gdy Twoje programy będą coraz bardziej rozbudowane i będą manipulowały większą ilością danych, wybór implementacji może być istotny. Bardzo ciekawie różnice są opisane przez użytkowników na Stacku.

Inicjalizacja listy może wyglądać na przykład w taki sposób:

Możesz zauważyć dwie rzeczy, których wcześniej nie było:

  • po jednej stronie mamy List, po drugiej ArrayList/LinkedList – wtf? o ile klasa dziedziczy bądź implementuje zadeklarowaną klasę/interfejs, możemy to w ten sposób deklarować (i tak powinniśmy). o ile jednak po obu stronach wystąpi ArrayList/LinkedList, też zadziała. Bardziej szczegółowo opiszę to w części poświęconej programowaniu obiektowemu
  • w trójkątnych nawiasach występuje “String”. Lista w Javie jest generyczna. Co to oznacza? Wszystko w swoim czasie. W tym przypadku załóżmy, iż tak określamy jakiego typu elementy lista będzie przechowywać. Po drugiej stronie możesz zrobić tak samo lub użyć zapisu “diamentu” (czyli <>)

Ze względu na to, w jaki sposób klasa jest zaimplementowana, listę tablicową możemy jeszcze zadeklarować w taki sposób:

Czym jest initSize w tym przypadku? Jest to początkowy rozmiar tablicy, jaką lista posiada pod spodem. Domyślny rozmiar to 10, ale gdy wiesz, iż będziesz potrzebować większą/mniejszą tablicę i wiesz o ileo, warto zadeklarować to w ten sposób.

Jak używać list?

Poniżej znajduje się prosty program wykonujący podstawowe operacje na listach::

Co tu się dzieje? Spieszę z odpowiedzią:

  • na początku tworzę listę tablicową o początkowym rozmiarze tablicy pod spodem równym 5
  • sprawdzam rozmiar listy metodą size(). Możesz się zdziwić, ale zobaczysz 0. Dlaczego? Rozmiar listy to ilość elementów, a nie rozmiar tablicy. Lista wiązana tablicy pod spodem nie ma, więc to by nie miało sensu w tamtym przypadku.
  • dodaję element do listy – tekst “Hi!”
  • sprawdzam jeszcze raz rozmiar listy – już wynosi 1
  • biorę pierwsz element (indeks 0, ta sama zasada co w tablicach), zobaczę “Hi!”
  • tworzę drugą listę, wiązaną tym razem
  • dodaję kolejno 3 elementy do secondList
  • sprawdzam metodą size() rozmiar – dodałem 3 elementy, a więc rozmiar też wynosi 3
  • sprawdzam wartości elementów pierwszego i drugiego metodą get (odpowiednio indeksy 0 i 1)
  • dodaję do list WSZYSTKIE elementy z secondList dzięki metody addAll, do której jako argument podaję listę, z której elementy chcę dodać
  • wcześniej list miała 1 element, przed chwilą dodałem elementy z trójelementowej listy. Oczekiwany rozmiar listy to 4. Metoda size() prawdę nam powie – lista ma 4 elementy w tym momencie
  • chcę sprawdzić czy lista jest pusta. Nie muszę tego sprawdzać dzięki warunków (o czym już w następnej części), metoda isEmpty() zwraca true o ile lista jest pusta lub false, o ile nie jest (czym jest true/false? Wybacz, też następnym razem :)). Na tą chwilę mamy w liście 4 elementy. Na pewno więc nie jest pusta
  • chcę się dowiedzieć czy lista posiada tekst “Hi!”. Metoda contains() prawdę Ci powie gdy podany element do listy należy
  • chcę zmienić pierwszy element listy. W tym celu używam metody set(), która jako pierwszy argument przyjmuje indeks pod którym w liście chcemy ustawić element, a jako drugi argument przyjmuje obiekt, który chcemy tam przypisać
  • sprawdzam, czy element został odpowiednio ustawiony metodą get(). Ten sam indeks jak wcześniej, a więc i oczekiwany obiekt zostaje zwrócony
  • ponownie używam metody contains dla “Hi!”. Tym razem dostaniemy false, gdyż element został przeze mnie nadpisany
  • co o ile chcę się dowiedzieć w którym miejscu znajduje się konkretny element? Metoda indexOf() przyjmuje element i zwraca indeks pod którym podany element się znajduję. Dla ustawionego przeze mnie przed chwilą “Hiho!” otrzymamy 0, bo w tym miejscu to ustawiliśmy. Dla “Hi!” już niestety mamy -1. Oznacza to, iż element NIE NALEŻY do listy
  • lista ma 4 elementy w tym momencie, a więc mogę manipulować elementami pod indeksami 0, 1, 2 oraz 3. Chcę się pozbyć jednak elementu spod indeksu 2. Mogę użyć metody remove, która przyjmuje liczbę jako indeks lub konkretny element (jezeli jest kilka takich samych, usunie pierwszy z kolei). W tej chwili podaję indeks
  • sprawdzam rozmiar metodą size() – już są 3 elementy
  • usuwam element “Hiho!”, który niedawno przypisałem. Używam ponownie metody “Hiho!”, ale nie podaję już liczby 0 jako indeks z którego element chcę usunąć tylko właśnie tekst do usunięcia. Tak się składa, iż jeszcze należy do listy, więc usuwanie przebiega prawidłowo
  • ponowne sprawdzenie rozmiaru metodą size() – coraz mniej tego, już tylko 2 elementy!
  • to co, może demolka? Usuwam WSZYSTKIE elementy z listy. Czy trzeba wywoływać remove() tyle razy, ile jest elementów? Nie! Metoda clear() czyści całą listę
  • dla potwierdzenia sprawdzam rozmiar metodą size() – mamy 0
  • hmm… jest 0, a więc lista jest pusta, prawda? Metoda isEmpty() mówi, iż tak

Podsumowanie

Podsumowanie? Tak bez zadania domowego? Dzisiaj tak. W części następnej dostaniesz zadanie, które będzie z materiału z dwóch części. Jak widzisz, tablice i listy mają wiele użytecznych metod i funkcjonalności. Znajdują zastosowanie praktycznie w każdym programie i mogą ułatwić programiście życie na wiele sposobów. Tablice jak i listy mogą być używane efektownie w połączeniu z pętlami oraz instrukcjami sterującymi. Jak? O tym już w następnej części!

<- [#03] – Znaki tekstowe

Idź do oryginalnego materiału