Tablice. Wprowadzenie do kolekcji.


1. Pojęcie tablicy. Realizacja tablic w Javie

1.1. Tablice w Javie. Deklarowanie i tworzenie


Dane w programie mogą  być organizowane w różny sposób. W szczególności  jako zestawy (powiązanych i/lub w okreslony sposób uporządkowanych) wartości. W tym kontekście mówimy o strukturach danych.
Jednym z ważnych rodzajów struktur danych - są tablice.

Tablice są zestawami elementów (wartości) tego samego typu, ułożonych na określonych pozycjach. Do każdego z tych elementów mamy bezpośredni (swobodny - nie wymagający przeglądania innych elementów zestawu) dostęp poprzez nazwę tablicy i pozycję elementu w zestawie, określaną przez indeks lub indeksy tablicy.


Na przykład, tablica czterech liczb całkowitych może wyglądać tak.
r

Pierwszy element - liczba 21 ma indeks 0, drugi - liczba 13 indeks 1 itd.
Do elementów tablicy odwołujemy się za pomocą nazwy tablicy oraz  indeksu umieszczonego w nawiasach kwadratowych.
Jeżeli ta tablica ma nazwę tab, to do pierwszego elementu odwołujemy się poprzez nazwę tablicy i indeks 0: tab[0], do drugiego - tab[1] itd. Jak widać, odwołanie np. do 3-go elementu - nie wymaga przeglądania innych elementów.


W Javie tablice są obiektami, a nazwa tablicy jest nazwą zmiennej, będącej referencją do obiektu-tablicy. Obiekt-tablica zawiera elementy tego samego typu. Może to być dowolny z typów prostych lub referencyjnych. Zatem, w szczególności elementami tablic mogą być referencje do innych tablic. Mamy wtedy do czynienia z odpowiednikiem tablic wielowymiarowych.

Tak samo jak wszystkie inne zmienne - tablice musimy deklarować przed użyciem ich nazw w programie.

Deklaracja tablicy składa się z:
Np.
        int[]  arr;   // jest deklaracją tablicy liczb całkowitych (typu int),
        String[] s;  // jest deklarację tablicy referencji do obiektów klasy String
        JButton[] b; // jest deklarację tablicy referencji do obiektów klasy JButton
        double[][] d;  // jest deklaracją dwuwymiarowej tablicy liczb rzeczywistych

Ściślej można powiedzieć, że deklarowane są tu zmienne tablicowe.
Typ takiej zmiennej jest typem referencyjnym, a jego nazwa składa się z nazwy typu elementów tablicy i nawiasów kwadratowych. W powyższych przykładach:
    zmienna arr jest typu int[]
    zmienna s jest typu String[]
    zmienna d jest typu double[][]


Uwaga: rozmiar tablicy nie stanowi składnika deklaracji tablicy
Np. taka deklaracja:
    int[5] arr;  
jest niedopuszczalna.

Skoro tablice są obiektami - to jakich klas? Otóż w trakcie kompilacji programu niejawnie tworzone są definicje klas dla tablic. Klasy te mają specjalne nazwy - tylko dla potrzeb JVM (np. klasa opisująca jednowymiarową tablicę liczb całkowitych ma nazwę [I) i jedno pole - stałą typu int o wartości równej liczbie elementów tablicy
Jeżeli oswoimy się z myślą, że tablice są obiektami, to - przez analogię do innych obiektów - będzie nam łatwo zrozumieć różnicę pomiędzy deklaracją i utworzeniem tablicy.

Deklaracja tablicy tworzy referencję.
 
int[] arr;   // arr jest referencją
               // arr jest zmienną typu int[], który jest typem referencyjnym

Taka deklaracja nie alokuje pamięci dla samej tablicy!

Pamięć jest alokowana dynamicznie albo  przy deklaracji z inicjacją za pomocą nawiasów klamrowych albo w wyniku użycia wyrażenia new.



Deklaracja tablicy z inicjacją za pomocą nawiasów klamrowych ma postać:

     typ[] zm_tab =  { wart_1, wart_2, .... wart_N }

gdzie:
            typ - typ elementów tablicy,
            zm_tab - nazwa zmiennej tablicowej,
            wart_i  - wartość i-go elementu tablicy


Np.

        int[] arr = { 1, 2, 7, 21 };

deklaruje tablicę o nazwie arr, tworzy ją i inicjuje jej elementy; kolejno:
  1. Wydzielana jest pamięć dla zmiennej arr, która będzie przechowywać referencję do obiektu-tablicy.
  2. Wydzielana jest pamięć (dynamicznie, na stercie) potrzebna do przechowania 4 liczb całkowitych (typu int).
  3. Kolejne wartości 1,2,7,21 są zapisywane kolejno w tym obszarze pamięci.
  4. Adres tego obszaru (referencja) jest przypisywany zmiennej arr.
 Drugi sposób utworzenia tablicy polega na zastosowaniu wyrażenia new.


Tworzenie tablicy za pomocą wyrażenia new (bez inicjacji elementów) ma postać

            new T[n];

gdzie:


Uwaga: nawiasy są kwadratowe, a nie okrągłe, jak w przypadku użycia new z konstruktorem jakiejś klasy
Na przykład:

 int[] arr;               // deklaracja tablicy
 arr = new int[4];   // utworzenie tablicy 4 elementów typu int

Można to też zapisać od razu w wierszu deklaracji:

 int[] arr = new int[4];

Mechanizm działania jest tu identyczny jak w przypadku innych obiektów.
Przypomina go poniższy rysunek.

Rys


Zauważmy, że rozmiar tablicy może być ustalony dynamicznie, w fazie wykonania programu. Np.

int n;
//... n uzyskuje wartość
// np. na skutek obliczeń opartych na wprowadzonych przez użytkownika danych
//...
int[] tab = new int[n];

Ale - uwaga - po ustaleniu rozmiar nie może być zmieniony.


Elementy tablic tworzonych za pomocą wyrażenia new T[n]  mają inicjalne wartości ZERO (zera arytmetyczne, false dla typu boolean, null dla typów referencyjnych).

Wyrażenia new można też użyć do utworzenia i równoczesnej inicjacji elementów tablicy wartościami zapisanymi w nawiasach klamrowych.


Tworzenie tablicy za pomocą wyrażenia new (z inicjacją elementów) ma postać

            new T[] { wart_1, wart_2, .... wart_N };


gdzie:

Np.

int[] a;
// ....
a = new int[] {1, 2, 3, 4 };

Jest to szczególnie wygodne, gdy chcemy ad hoc utworzyć i zainicjować tablicę elementami  np. przy przekazywaniu tablicy jako argumentu jakiejś metodzie.
Przykladowo, w kontekście:

// definicja metody, działającaj na tablicy przekazanej jako argument
void metoda(int[] tab) {
  //....
}
// ...
// wywołanie metody z ad hoc utworzoną tablicą
metoda(new int[] { 7, 7, 7, 7 });

1.2. Odwołania do elementów tablic


Jak już wspomniano do elementów tablic odwołujemy się za pomocą indeksów.


Indeksy tablicy mogą być wyłącznie wartościami typu int.

Mogą być dowolnymi wyrażeniami, których wyliczenie daje wartość typu int.

Tablice zawsze indeksowane są poczynając od 0.
Czyli  pierwszy element n-elementowej tablicy ma indeks 0,
a ostatni - indeks n-1.


Ze względu na to, że wartości typu byte, char i short są w wyrażeniach "promowane" (przekształcane) do typu int), to również wartości tych typów możemy używać przy indeksowaniu tablic. Niedoposzczalne natomiast jest użycie wartości typu long.

Odwołanie do i-go elementu tablicy o nazwie tab ma postać:

                tab[i]

Ta konstrukcja składniowa traktowana jest jako zmienna, stanowi nazwę zmiennej - zatem możemy tej zmiennej przypisywać wartości innych wyrażeń oraz możemy używać jej wartości w innych wyrażeniach



Na przykład:
int[] a = new int[3];
a[1] = 1;  // nadanie DRUGIEMU elementowi tablicy a wartości 1
int c = a [1] + 1;  // c będzie miało wartość 2
int i = 1, j = 1;
a[i +j] = 7;  // nadanie elementowi o indeksie i+j (=2) wartości 7

Odwołania do elementów tablic są przez JVM sprawdzane w trakcie wykonania programu pod względem poprawności indeksów. Java nie dopuści do odwołania  do nieistniejącego elementu tablicy lub podania indeksu mniejszego od 0. Próba takiego odwołania spowoduje powstanie wyjątku  ArrayIndexOutOfBoundsException, na skutek czego zostanie wyprowadzony odpowiedni komunikat i wykonanie programu zostanie przerwane (ew. taki wyjątek możemy obsłużyć).

Zobaczmy przykład.
public class Test {

  public static void main(String[] args) {
    int[] a = {1, 2, 3, 4 };
    System.out.println(a[4]);
    System.out.println(a[3]);
    System.out.println(a[2]);
    System.out.println(a[1]);
  }

} 
Zauważmy - mamy tu tablicę składającą się z 4 liczb całkowitych. Chcemy po kolei wyprowadzić jej elementy od ostatniego poczynając. Częstym błędem jest zapominanie o tym, że tablice indeksowane są od zera: w tym programie zapomniano o tym i próbowano odwołać się do ostatniego elementu tablicy a za pomocą a [4] (ktoś pomyślał: skoro są cztery elementy - to ostatni jest a[4]). Tymczasem jest to odwołanie poza zakres tablicy, do nieistniejącego 5-go elementu! Ten błąd zostanie wykryty, na konsoli pojawi się komunikat i program zostanie przerwany.

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at Test.main(Test.java:5)


W powyższym przykładzie było nieco żmudne wypisywanie kolejnych elementów tablicy. W naturalny sposób powinniśmy to robić w pętli.

Może tak?

public class Test {

  public static void main(String[] args) {
    int[] a = {1, 2, 3, 4 };
    for (int i=3; i>=0; i--) System.out.println(a[i]);
  }

}
A co się stanie gdy zmienimy rozmiar tablicy dodając kilka nowych elementów w inicjacji? Będziemy musieli od nowa  policzyć elementy i zmienić inicjację licznika pętli. Trochę niewygodne, a do tego naraża nas na błędy. A przecież rozmiar tablicy znany jest JVM, niech zatem "liczeniem" elementów zajmuje się komputer.

Zawsze możemy uzyskać informacje o rozmiarze (liczbie elementów) tablicy za pomocą odwołania:
 
                nazwa_tablicy.length

Uwaga: częstym błędem jest traktowanie tego wyrażenia jako wywołania metody. W tym przypadku length nie jest nazwą metody (lecz pola niejawnie stworzonej klasy, opisującej tablicę), dlatego NIE STAWIAMY po nim nawiasów okrągłych


Zatem poprzedni program można by zapisać tak:
public class Test {

  public static void main(String[] args) {
    int[] a = {1, 2, 3, 4 };
    for (int i=a.length-1; i>=0; i--) System.out.println(a[i]);
  }

}

 
Spróbujmy teraz odwrócić kolejność wypisywania elementów tablicy (czyli po kolei od pierwszego poczynając).
Jak powinna wyglądać pętla for?


public class Test {

  public static void main(String[] args) {
    int[] a = {1, 2, 3, 4 };
    for (int i=0; i<a.length; i++) System.out.println(a[i]);
  }

}


Przebięgając w pętli przez wszystkie (poczynając od pierwszego) elementy tablicy tab musimy zmieniać indeksy od 0 do tab.length-1, czyli zastosować następującą postać pętli for:

for (int i = 0; i < tab.length; i++) ... tab[i] ... ;



Użycie length wobec tablicy jest szczególnie wygodne w metodach, które otrzymują jako argumenty referencje do tablic: możemy w ten sposób pisać uniwersalne metody działające na tablicach o różnych rozmiarach.

2. Użycie zmiennych tablicowych w przypisaniach. Zmienne tablicowe jako argumenty i wyniki metod.

Ponieważ zmienna oznaczająca tablicę zawiera referencje do tablicy, to - pod pewnymi warunkami -  możemy jej przypisać referencję do innej tablicy.

Zmiennej tablicowej typu typA[] można przypisać wartość zmiennej tablicowej typu typB[] pod warunkiem, że dopuszczalne jest przypisanie wartości typu B zmiennej typu A.
Każdej zmiennej tablicowej - jak każdej zmiennej zawierającej referencję - można przypisać wartość null


W bardzo naturalny sposób możemy przypisywać referencje w przypadku gdy mamy do czynienia z tym samym typem tablic (a zatem - z tym samym typem elementów oraz z tą samą liczbą wymiarów).


Tak samo jak w przypadku innych obiektów - nie należy mylić przypisania zmiennych tablicowych (czyli referencji)  z kopiowaniem zawartości tablic.


Na przykład poniższy program:
public class Test {

  public static void main(String[] args) {
    byte[] b1 = {1, 2, 3 };
    byte[] b2 = {1, 2, 3, 5, 5 };
    byte[] b = b2;
    b2 = b1;
    b2[0] = 77;
    b[0] = 99;
    System.out.print("\nTablica \"b1\":");
    for (int i=0; i < b1.length; i++) System.out.print(" " + b1[i]);
    System.out.print("\nTablica \"b2\":");
    for (int i=0; i < b2.length; i++) System.out.print(" " + b2[i]);
    System.out.print("\nTablica \"b\":");
    for (int i=0; i < b.length; i++) System.out.print(" " + b[i]);
  }

}
Wyprowadzi pokazane wyniki:
Tablica "b1": 77 2 3
Tablica "b2": 77 2 3
Tablica "b": 99 2 3 5 5

Zwróćmy uwagę:

W tym przykładzie przez chwilę mogło dziwić, że "na tablicę" b2 "podstawiamy tablicę" (o innych rozmiarach!) - b1. Nie przeczy to jednak zasadzie, że - po ustaleniu - rozmiary tablic nie mogą być zmieniane. No, tak - przecież przypisanie dotyczy referencji do tablic, a nie samych tablic. W przypadku referencji różne rozmiary tablic na które one wskazują nie są żadną przeszkodą przy przypisaniu - rozmiary tablic pozostają bez zmian.
I druga ważna kwestia, wymagająca jeszcze raz szczególnego podkreślenia: na jedną tablicę może wskazywać kilka zmiennych tablicowych. Za pomocą każdej z nich (i operacji indeksowania) możemy zmieniać wartości elementów tej jednej tablicy, a odwolania do tej tablicy poprzez inne zmienne będą - oczywiście - uwzględniać te zmiany (np. pokazywać zmienione wartości).

Przekazanie argumentów metodzie i zwrot jej wyniku mogą być traktowane jako szczególny rodzaj operacji przypisania.
Zatem metody mogą działać na tablicach, do których referencje otrzymują w postaci parametrów i mogą zwracać wyniki - referencje do tablic.

W przypadku tablic jednowymiarowych typem zmiennych "tablicowych" (inaczej referencji do tablic) jest T [], gdzie T - nazwa typu elementów tablicy. Zatem w nagłówku metod - parametry (które mają oznaczać tablice; są referencjami do tablic) deklarujemy właśnie za pomocą takich nazw typu. Oczywiście, gdy wywołujemy metodę - to na liście argumentów podajemy nazwy zmiennych tablicowych (już bez nawiasowych kwadratowych).
Jeśli metoda zwraca wynik - referencję do tablicy, to typem wyniku jest również odpowiedni typ tablicowy.

Np. metoda o nazwie dblVal, która zwraca referencję do nowoutworzonej tablicy liczb całkowitych, wartości elementów której są podwojonymi wartościami elementów tablicy liczb całkowitych, do której referencja przekazana została metodzie jako argument, może być zdefiniowana i użyta tak:

public class Test {

  Test() {
    int[] a = {1, 2, 3, 4 };
    int[] wynik = dblVal(a);
    for (int i=0; i < wynik.length; i++)
      System.out.print(" " + wynik[i]);
  }

  int[] dblVal(int[] tab) {
    int[] w = new int[tab.length];  // utworzenie tablicy "pod wynik"
                                    // jej rozmiary muszą być równe
                                    // rozmiarom tablicy-argumentu
    for (int i=0; i < w.length; i++) w[i] = tab[i]*2;
    return w;
  }

  public static void main(String[] args) {
    new Test();
  }

}

Dobremu zrozumieniu przekazywania argumentów tablicowych i zwracania tablicowych wyników powinien sprzyjać poniższy rysunek:

R


Proszę zwrócić uwagę jak niezręczny był opis działania metody dblVal:
"zwraca referencję do nowoutworzonej tablicy liczb całkowitych, wartości elementów której są podwojonymi wartościami elementów tablicy liczb całkowitych, do której referencja przekazana została metodzie jako argument"
Aby podkreślić sens metody dblVal powinniśmy raczej powiedzieć - nie tylko nieprecyzyjnie, ale nawet nieprawdziwie:
"Metoda dblVal zwraca tablicę, wartości elementów której są podwojonymi wartościami elementów tablicy przekazanej jako argument".

Często dla uproszczenia będziemy mówić, że metoda otrzymuje jako argument - tablicę i zwraca jako wynik - tablicę. Jest to skrót myślowy: pamiętajmy, że zawsze chodzi o referencje do tablic


Pamiętamy, że w Javie argumenty przekazywane są metodom przez wartość.
Gdy argumentem jest zmienna tablicowa - przekazywana jest referencja do tablicy - i tej oczywiście w metodzie nie jesteśmy w stanie efektywnie zmienić. Nic jednak nie stoi na przeszkodzie, by zmienić elementy przekazanej tablicy.

Obrazuje to poniższy program.
public class Test3 {

  public static void main(String[] args) {
    new Test3();
  }

  Test3() {
    int[] tab = { 2, 5, 7 };
    chgTab1(tab);
    showTab("Po wywołaniu metody chgTab1 tablica oznaczana przez tab", tab);
    chgTab2(tab);
    showTab("Po wywołaniu metody chgTab2 tablica oznaczana przez tab", tab);
  }

  void chgTab1(int[] tab) {
    int[] nowa = { 3, 6, 8 };
    tab = nowa;
    showTab("W metodzie chgTab1 tablica oznaczana przez tab", tab);
  }

  void chgTab2(int[] tab) {
    for (int i=0; i < tab.length; i++) tab[i]++;
  }

  void showTab(String s, int[] tab) {
    System.out.println(s);
    for (int i=0; i < tab.length; i++) System.out.print(" " + tab[i]);
    System.out.print('\n');
  }

}

który da następujący wynik:

W metodzie chgTab1 tablica oznaczana przez tab
 3 6 8
Po wywołaniu metody chgTab1 tablica oznaczana przez tab
 2 5 7
Po wywołaniu metody chgTab2 tablica oznaczana przez tab
 3 6 8



3. Rozszerzona instrukcja for dla tablic

Rozszerzona instrukcja znana jest rownież jako instrukcja "for-each". Służy do przebiegania po zestawach danych (kolekcjach, tablicach, innych). Dla każdego elementu takiego zestawu wykonywane są instrukcje zawarte w ciele rozszerzonego for. 


Składnia

        for ( Typ id : expr )
            stmt

gdzie:

Np.
double[] nums = { 1, 2, 3 };
for (double d : nums)  System.out.println(d + 1);

wypisze w kolejnych wierszach 2.0, 3.0 i 4.0

String[] names = { "A", "B", "C" }
for (String s : names)  System.out.println(s);

wypisze w kolejnych wierszach A, B, C.

Nieprzypadkowo w opisie składni "for-each" mówi się o "wyrażeniu, ktorego typem jest typ tablicowy".
Naturalnie, zmienna tablicowa jest wyrażeniem typu tablicowego, ale będą nim również:

Pokazuje to poniższy program (w którym też jeszcze raz przyjrzymy się róznym metodom tworzenia tablic).
import java.util.*;

public class DeclCreSamples {

  // Metoda wypisująca elementy tablicy
  // przekazanej jako argument
  private static void show(int[] a) {
    for (int elt : a) {               // rozszerzone for 
      System.out.print(elt + " ");
    }
    System.out.println();
  }
  
  // Metoda tworzy tablice napisow w postaci
  // 1.a 1.b 1.c ...
  private static String[] generateStringTab(int n) {
      String[] stab = new String[n];
      for (int i = 0; i < stab.length; i++) {
        stab[i] = i + 1 + "." +  (char)('a' + i);
      }
      return stab;
    }
  
  public static void main(String[] args) {
    // Deklaracja z inicjacją
    int[] a1 = {1, 2, 3, 4 };
    show(a1);
    
    // Deklaracja tablicy n-elementowej
    Scanner sc = new Scanner(System.in);
    System.out.println("Podaj rozmiar tablicy");
    int n = sc.nextInt();
    int[] a2 = new int[n];
    // nadanie wartości elementom tablicy
    for (int i = 0; i < a2.length; i++) {
      a2[i] = n;
    }
    show(a2);
    
    // Tworzenie tablicy z inicjacją ad hoc
    show (new int[] { 7, 9, 11 });
    
    // W for-each użyjemy wyrażenia new
    for (boolean b : new boolean[] { true, false, true } ) {
      System.out.print(!b + " ");
    }
    System.out.println();
    
    // W for-each użyjemy wywolania metody zwracającej referencję do tablicy
    for (String s : generateStringTab(5)) System.out.print(s + " ");
  }

}
Dzialanie programu (widok konsoli):
1 2 3 4
Podaj rozmiar tablicy
3
3 3 3
7 9 11
false true false
1.a 2.b 3.c 4.d 5.e

Przypomnijmy w tym kontekście, że dla typów wyliczeniowych dostępna jest statyczna metoda values(), która zwraca zestaw wartości (stałych) danego typu jako tablicę.
Zatem poniższy program:
public class EnumsVals {
  
  enum Month { 
    JAN, FEB, MAR, APR, MAY, JUN, 
    JUL, AUG, SEP, OCT, NOV, DEC
  }

  public static void main(String[] args) {
    
    for (Month m : Month.values()) {
      System.out.print(" " + m);
      if (m == Month.JUN) System.out.println();
    }

  }

}  
wyprowadzi na konsolę:
 JAN FEB MAR APR MAY JUN
 JUL AUG SEP OCT NOV DEC


Warto zauważyć, że instrukcja "for-each" :

for ( Typ id : expr )
            stmt


w przypadku tablic jest równoważna następującemu zapisowi:

    Typ[] $a = expr;
    for (int $i = 0; $i < $a.length; $i++) {
        Typ id = $a[ $i ] ;
        stmt
    }
Uwaga: symbolem $ oznaczane są tymczasowe, wewnętrzne zmienne, niedostępne w ciele instrukcji for-each.

Oznacza to, że rozszerzone for ma swoje ograniczenia.  W ciele (zestawie instrukcji rozszerzonego for) mamy dostęp do elementów tablicy, ale nie możemy ich zmieniać.
Istotnie, taki zapis nie ma sensu:

int[] tab = {1, 2, 3 };
for(int a : tab) a = a*2;

bowiem a jest zmienną lokalną w ciele instrukcji for-each.

Oczywiście nie mamy też dostępu do indeksów kolejnych elementów (numerów iteracji).
Wprowadzenie i rozwój domknięć w nowych wersjach Javy może zmienić tę sytuację i być może uzyskamy strukturę sterująca znaną z innych języków jako "forEachWithIndex".

4. Metody ze zmienną liczbą argumentów

Zastosowanie wyrażenia new wraz z inicjatorem klamrowym do tworzenia tablic pozwala symulować wywołanie metod ze zmienną liczbą argumentów.
Mając np. metodę sum, która sumuje liczby całkowite z przekazanej jako argument tablicy:
  public static long sum(int[] args) {
    long sum = 0;
    for (int n : args) {
      sum += n;
    }
    return sum;
  }
możemy ją wywołać raz z trzema liczbami "do zsumowania":
long suma = sum( new int[] { 1, 2, 3} );
a innym razem z dwoma:
suma = sum( new int[] { 10, 21} );

Poczynając od wersji 1.5 w Javie  łatwiej (i bezpośrednio) deklarujemy i wywołujemy metody ze zmienną liczbą argumentów.


Deklaracja metody ze zmienną liczbą argumentów

Zmienną liczbę argumentów oznacza się za pomocą wielokropka (...):

        typ ... parms


 gdzie typ - typ argumentów,

           parms - oznacza argumenty, których może być  0, jeden lub wiele.

Taki zapis powinien być ostatni na liście argumentów (parametrów) metody.

Np.
typWyniku nazwaMetody(typ1 parm1, typ2 parm2,  typ ... parms)
typWyniku nazwaMetody( typ ... parms)




Dostęp w metodzie do przekazanych argumentów:


        for ( typ v : parms) ...


        parms[0], parms[1] ...


        parms.length



Jak łatwo można się domyślić kompilator ukrywa przed nami zwykłą tablicę parametrów (nazwaMetody(typ[] parms).


Jak można wywoływac metodę ze zmienną liczbą argumentów

Podając argumenty  - różną liczbę przy różnych wywołaniach:

metoda(a);
metoda(a, b, c);


A także podając tablicę:

String[] tab = ....
metoda(tab);

Poprzedni przykład zapiszemy teraz w nieco przyjemniejszej formie.
public class VarArg2 {
  
  public static long sum(int ... args) {
    long sum = 0;
    for (int n : args) {
      sum += n;
    }
    return sum;
  }

  public static void main(String[] args) {
    long suma = sum( 1, 2, 3 );
    System.out.println(suma);
    suma = sum( 10, 21 );
    System.out.println(suma);
  }

}


5. Argumenty wiersza poleceń

Obowiązkowym parametrem metody public static void main(...) jest referencja do tablicy łańcuchów znakowych - String[].
Po uruchomieniu programu, tablica ta zawiera słowa podane w wierszu poleceń jako argumenty wywolania programu.

Co znaczy: "argumenty wywołania"?
Weźmy dowolną aplikację, np. notepad.
W sesji znakowej - inaczej terminalu  (w Windows - "okienko DOS") możemy go uruchomić przeglądarkę za pomocą polecenia notepad.
Dodatkowo możemy podać nazwę pliku, który ma być w edytorze otwarty np.:

    notepad tekst.txt

Ten dodatkowy napis - to właśnie argument wywołania programu.

Uwaga. Korzystając ze środowisk uruchomieniowych nalezy sprawdzić jak w konkretnym środowisku podaje się argumenty wiersza poleceń. Zwykle będzie to opcja w którymś z menu o nazwie  "command line arguments"
Teraz - jeśli mamy program napisany w Javie:

public class Program {
 //....
  public static void main(String[] args) {
   // ...
  }
}

to - po kompilacji - w wywołania programu możemy podać w wierszu poleceń argumenty:

    java Program arg1 arg2 ...  

i podane w wierszu poleceń argumenty - będą dostępne w metodzie main jako elementy tablicy args, przy czym pierwszy argument będzie pierwszym elementem tablicy (elementem o indeksie 0), drugi - drugim itd.

W wielu systemach operacyjnych argumentami są kolejne słowa podane po wywołaniu aplikacji. Jeśli chcemy przekazać napis składający się z kilku słów - to należy ująć go w cudzysłów






Przyklad 1. Wypisać argumenty wywołania programu

public class Test {

  public static void main(String[] args) {
    System.out.println("Liczba argumentów wywołania: " + args.length);
    for (String s : args)
      System.out.println(s);
  }
}


Przykład 2. Zabezpieczyć się przed niewłaściwym wywołaniem, gdy wiadomo, że mają być dwa argumenty:
public class Test {

 public static void main(String[] args) {
   if (args.length != 2) syntax();
   System.out.println(args[0] + " " + args[1]);
 }

 static void syntax() {
   System.out.println("Syntax: ... ");
   System.exit(1);                     // metoda exit(c) kończy dzialanie aplikacji z kodem c
 }

}

Innym sposobem reagowania na niedostatek argumentów może być obsługa wyjątku ArrayIndexOutOfBoundsException:
public class Args {

  public static void main(String[] args) {
    try { 
      System.out.println(args[0] + " " + args[1]);
    } catch (ArrayIndexOutOfBoundsException exc) {
        syntax();
    }
    
  }

  static void syntax() {
    System.out.println("Syntax: ... ");
    System.exit(1);
  }

}
 

6. Tablice "obiektowe"

6.1. Tworzenie tablic zwierających referencje do obiektów


Jak wiemy już, i jak widzieliśmy przed chwilą - tablice mogą zawierać referencje do dowolnych obiektów.
Bardzo naturalnie wygląda to w przypadku tablic elementów typu String.
Możemy np. zadeklarować i stworzyć tablicę:

String[] town = { "Warszawa", "Poznań", "Kraków", "Gdańsk" };

i operować na jej elementach, np. wypisać je:

for (int i=0; i<town.length; i++) System.out.println(town[i]);

albo przestawić miejscami pierwszy i ostatni element:

String last = town[town.length -1];
town[town.length -1] = town[0];
town[0] = last;


Składniowo wygląda to identycznie jak operowanie na tablicach liczb całkowitych czy rzeczywistych. Wydaje się, że nie ma różnicy.
Jednak różnica jest, a niedostrzeganie jej prowadzi do częstych błędów, szczególnie w początkowej fazie nauki języka Java.

Przypomnijmy sobie klasę Para z rozdziału 4. Jej obiekty reprezentują pary liczb całkowitych.
Powiedzmy, że chcielibyśmy w programie operować na tablicy par liczb całkowitych .

Przede wszystkim konieczna jest deklaracja:

Para[] tabPar;

Dobrze już wiemy, że taka deklaracja nie tworzy tablicy.
Zatem następny krok - stworzenie tablicy

tabPar = new Para[10];

Czy mamy już obiekty-pary? Czy możemy zobaczyć jak wyglądają "na samym początku"?
Zobaczmy.
public class TabPar {

  public static void main(String[] args) {
    Para[] tabPar = new Para[10];
    for (int i=0; i < tabPar.length; i++) tabPar[i].show("Para " + (i+1));
   }

}
Ten program skompiluje się bezbłędnie, ale przy jego wykonaniu otrzymamy następujący komunikat.

Exception in thread "main" java.lang.NullPointerException
        at TabPar.main(TabPar.java:6)


Jest on skutkiem odwołania do nieistniejącego obiektu - wywołania metody show z klasy Para za pomocą referencji o wartości null.

Faktycznie, przecież obiekty trzeba tworzyć!. Stworzenie tablicy nie tworzy obiektów, które chcielibyśmy traktować jako jej elementy. 

Tablica przechowuje referencje do obiektów
, czyli jej elementy na początku będą miały domyślne wartości null - a dopiero po stworzeniu obiektów i przypisaniu referencji (ich adresów) elementom tablicy będziemy mogli używać elementów tablicy w operacjach na obiektach.

Powinniśmy zatem napisać coś w rodzaju:
public class TabPar {

  public static void main(String[] args) {
    Para[] tabPar = new Para[10];
    for (int i=0; i < tabPar.length; i++) tabPar[i] = new Para(i+1, i+2);
    for (int i=0; i < tabPar.length; i++) tabPar[i].show("Para " + (i+1));
   }

}
co w wyniku da następujacy wydruk:

Para 1 ( 1 , 2 )
Para 2 ( 2 , 3 )
Para 3 ( 3 , 4 )
Para 4 ( 4 , 5 )
Para 5 ( 5 , 6 )
Para 6 ( 6 , 7 )
Para 7 ( 7 , 8 )
Para 8 ( 8 , 9 )
Para 9 ( 9 , 10 )
Para 10 ( 10 , 11 )


To co się dzieje wyjaśnia następujący rysunek.

r

Można przypuszczać, że w przypadku takiej klasy jak Para nie zdarzy nam się błąd "braku obiektów" (bowiem zwykle będziemy chcieli mieć jakieś konkretne pary i będziemy pamiętać, że trzeba je stworzyć za pomocą wyrażenia new).
Jednak  nie jest to tak oczywiste w przypadku wielu klas zawartych w standardowych pakietcha Javy.
Np.  możemy mieć do czynienia z zestawami przycisków (klasy Button czy JButton). Naturalne jest myślenie o nich jako o tablicach przycisków (zresztą taki jest sens - i tak często będziemy opisywać programy). Zatem -  możemy pomyśleć, że po:
Button b = new Button[10];
już mamy 10 przycisków i możemy coś z nimi robić.

Ale przycisków jeszcze nie ma i nasz program wpada w kłopoty.


Tworzenie tablic z elementami oznaczającycmi obiekty - podsumowanie
(na przykładzie klasy JButton)

  1. Deklaracja tablicy :  JButton[] b;
  2. Utworzenie tablicy:  b = new JButton[n];
  3. Tworzenie obiektów i przypisywanie referencji, które na nie wskazują - elementom tablicy, np.: for (int i=0; i<b.length; i++) b[i] = new JButton(); 
 


Naturalnie, również w przypadku tablic referencji do obiektów mozemy użyć inicjatorów klamrowych, które pozwalają - przy deklaracji - stworzyć i zanicjować tablicę. Robiliśmy to już zresztą przy okazji inicjacji tablicy miast literałami łańcuchowymi.

Można podać przyklady takich inicjacji:

    Para[] tabPara = { new Para(1,1), new Para(2,3), new Para(4,5) };
    JButton[] b = { new JButton("A"), new JButton("B") };
    String[] s = { "Ala", "Kot", "Pies" };
   
    Para p1 = new Para(2,4);
    Para p2 = new Para(7,8);
    Para[] tabP = { p1, p2 };

Tak naprawdę przykłady te nie różnią się między sobą. We wszystkich w/w inicjacjach zmiennych tablicowych w nawiasach klamrowych podajemy referencje do obiektów odpowiedniej klasy.

6.2. Tablice heterogeniczne

Połączmy ze sobą znane nam fakty i spróbujmy je wykorzystać.
  1. Każda klasa w Javie dziedziczy pośrednio lub bezpośrednio klasę Object.
  2. Zatem referencji do obiektu klasy Object możemy przypisać referencję do obiektu dowolnej innej klasy.
  3. Tablice mogą zawierać referencje do obiektów.
Wyobraźmy sobie teraz, że mamy jakąś tablicę, w której kolejnych elementach chcemy zapisywać dowolne informacje. Czy to możliwe? Ależ tak, właśnie dzięki wymienionym wyżej trzem właściwościom, pod warunkiem jednak że informacja ta będzie przedstawiana w sposób obiektowy (jako obiekty).

W tablicy elementów typu Object możemy przechowywać referencje do obiektów dowolnych klas.

Możemy zatem stworzyć tablicę, za pomocą której jednocześnie będziemy rejestrować np. i napisy (łańcuchy znakowe) i pary liczb całkowitych (obiekty klasy Para).

Object[] tab =  { "Tekst" , new Para(10,11) };
Ta właściwość jest szczególnie użyteczna wtedy, gdy w tablicach przechowujemy referencje do obiektów podklas jakiejś klasy bazowej.
W tym miejscu warto przypomnieć klasę Publication z wykładu 7 i dziedziczące ją klasy Book, Journal i CDisk. Obiektowe konwersje rozszerzające pozwalają przypisywać zmiennym oznaczającym obiekty klasy bazowej referencje do obiektów klas pochodnych. A ponieważ elementy tablic "obiektowych" zawierają referencje - to na przykład w przypadku naszych klas opisujących publikacje  - elementom jednej tablicy typu Publication[] można przypisać wartości różnych klas: Publication, Book, Journal i CDisk.

Elementy tablicy mogą zawierać referencje wskazujące na obiekty różnych klas, pod warunkiem, że klasy te dziedziczą tę samą klasę - określającą ogólny, niejako wspólny dla wszystkich, typ elementów tablicy


Dość zawikłaną treść najlepiej wyjaśni schematyczny przykład:

Publication[] p = new Publication[3];
p[0] = new Book(...);
p[1] = new Journal(...);
p[2] = new CDisk(...);

Możemy powiedzieć (stosując skrót myslowy): pierwszy element tablicy publikacji jest książką, drugi czasopismem, a trzeci - płytą CD. Jest to możliwe dlatego, że i książka i czasopismo i płyta CD są publikacjami (to znaczy oprócz tego, że obiekty te są obiektami specyficznych klas Book, Journal i CDisk - mogą być również traktowane jako obiekty klasy Publication, ponieważ wszystki trzy klasy dziedziczą klasę Publication).

Oczywiście, inicjacje są przypisaniami, zatem możemy pisać:

Publication[] p = { new Book(..), new Book(...), new Journal(...) };

Również przekazywanie argumentów i zwracanie wyników metod jest swoistym przypisaniem.
Zatem możemy łatwo napisać uniwersalną metodę, która liczy dochód ze sprzedzazy różnych rodzajóą publikacji podanych w tablicy przekazanej jako argument.

  // Zrwaac dochód jaki można uzyskać ze sprzedaży publikacji
  // przekazanych w tablicy (publikacje mogą być róznych rodzajów)
 
  public double getIncome(Publication[] p) {
    double d = 0;
    String opis = "";
    for (int i=0; i<p.length; i++) {
      d += p[i].getPrice() * p[i].getQuantity();
    }
    retun(d);
  }




7. Tablice wielowymiarowe

Bardzo krótko i syntetycznie omówimy teraz tablice wielowymiarowe.

Tablice wielowymiarowe w Javie realizowane są jako tablice elementów, będących referencjami do tablic.

Liczba wymiarów  określana jest przez liczbę nawiasów [].

Przykładowe sposoby deklaracji i inicjacji:

a. inicjacja w nawiasach klamrowych

        int[][] mac1 = { { 1, 2, 3, }, { 4, 5, 6, } };

b. dynamicznie

        int[][] mac2 = new int[n][m];

c. tablica składa się z wektorów o różnych rozmiarach, zadawanych  przez tablicę w


public class MultiArr {

 public static void main(String[] arg) {
   int w[] = { 2, 3, 4 };
   int n = 3;
   int[][] m3 = new int[n][];    // rozmiary wierszy
                                 // będą zmienne dynamicznie
   for(int i = 0; i < m3.length; i++) {
     m3[i] = new int[w[i]];
     for (int j = 0; j < m3[i].length; j++) m3[i][j] = i + j;
   }

   for (int i = 0; i < m3.length; i++) {
     System.out.println("Rozmiar " + i + "-go wiersza " + m3[i].length);
     String out = " ";
     for(int j = 0; j < m3[i].length; j++) out += " " + m3[i][j];
     System.out.println(out);
   }

 }

}

Programik wyprowadzi następujące napisy:
Rozmiar 0-go wiersza 2
  0 1
Rozmiar 1-go wiersza 3
  1 2 3
Rozmiar 2-go wiersza 4
  2 3 4 5



8. Wprowadzenie do kolekcji

Kolekcja jest obiektem, który grupuje elementy danych (inne obiekty) i pozwala traktować je jak jeden zestaw danych, umożliwiając jednocześnie wykonywanie operacji na zestawie danych np. dodawania i usuwania oraz przeglądania elementów zestawu

Naturalną realizacją koncepcji kolekcji jest poznana przed chwilą tablica.
Jest to jednak przypadek szczególny, niewystarczający wobec bogactwa różnych, użytecznych w programowaniu, struktur danych.
Dlatego w Javie, w pakiecie java.util, zdefiniowano narzędzia, służące do tworzenia i posługiwania się różnymi rodzajami kolekcji.

Rozpatrzymy teraz przykładowe zastosowania wybranych kolekcji z pakietu java.util. Będzie to rodzaj pragmatycznego wprowadzenia, w którym - ze względu na wstępny charakter - nie są stosowane właściwe zasady używania kolekcji. Na zasady te zwrócimy szczególną uwagę później (w wykładzie pt. "Kolekcje"). 
W przykładowych programach - po to by utrzymać minimalne rozmiary funkcjonującego kodu - nie stosujemy właściwych obiektowych konstrukcji (ograniczamy się zwykle do metody main()) i  unikamy obsługi wyjątków.  Jest to oczywiście sposób programowania, którego nie należy stosować w praktyce.

Wyobraźmy sobie, że wczytujemy z pliku listę firm.
W programie możemy przedstawić ją jako tablicę. Ale jaki rozmiar ma mieć ta tablica? Z góry nie wiemy: w jednym pliku może być 100 firm - w innym 3 miliony.
A tablice w Javie mają ustalone (przy tworzeniu), niezmienne rozmiary, zatem nie nadają się do przedstawienia listy firm (których raz może być kilka, innym razem kilka milionów).

Oczywiście, możemy "ręcznie" dynamicznie realokować tablicę, zwiększając (w miarę potrzeby) jej rozmiary i przepisując zwartość, ale jest lepszy i prostszy sposob.

O liście możemy myśleć jako o zestawie elementów, z których każdy znajduje się na określonej pozycji w zestawie.
Klasa ArrayList z pakietu java.util stanowi łatwe rozwiązanie problemu: jest ona konkretną realizacją listy w postaci tablicy o dynamicznie (w miarę potrzeby) zmieniających się rozmiarach. Dodajemy element elt do końca listy za pomocą metody add(elt), uzyskujemy element znajdujący się na pozycji ind za pomocą metody get(ind), możemy też dowiedzieć się ile elementów aktualnie zawiera lista za pomocą metody size().

Zatem krótki ilustracyjny programik, który tworzy listę firm, dodaje do niej dowolną liczbę elementów (nazw firm zapisanych w kolejnych wierszach pliku), po czym wyprowadza zawartość listy na konsolę mógłby wyglądać tak:

import java.util.*;
import java.io.*;

class Intro1 {

  public static void main(String args[]) throws IOException {
    Scanner scan = new Scanner(new File("firms.txt"));
    // Utworzenie obiektu klasy ArrayList
    ArrayList list = new ArrayList();
    while (scan.hasNextLine()) {
      String firm = scan.nextLine();
      // dodanie kolejnego elementu do listy
      list.add(firm);
    }
    // wyprowadzenie zawartości listy
    for (int i = 0; i < list.size(); i++)
      System.out.println(list.get(i));
  }
  
} 
Jeśli w pliku mamy zapisane w kolejnych wierszach: IBM, Sun, Sun, Apple to wynik będzie następujący:
IBM
Sun
Sun
Apple

Można też użyć innego (bardziej ogólnego i lepszego - dlaczego zobaczymy dalej) sposobu przeglądania elementów listy.

Iterator jest obiektem służącym do przeglądania kolekcji.


Od każdej kolekcji za pomocą metody iterator()  możemy uzyskać obiekt-iterator, służący do jej przeglądania, po czym za pomocą metody next() uzyskiwać dostęp do kolejnych elementów, a za pomocą metody hasNext() - sprawdzać, czy kolejny element można pobrać (czy nie dobiegliśmy do końca kolekcji). W naszym przykładzie listy firm użycie iteratora może wyglądać następująco:

    Iterator iter = list.iterator();
    while( iter.hasNext()) System.out.println(iter.next());
co da taki sam efekt w postaci wyprowadzenia listy firm.

Alternatywną - nieco krótszą - formą iterowania po kolekcji jest użycie instrukcji for:
     for (Iterator iter = list.iterator(); iter.hasNext(); )  System.out.println(iter.next());
Najlepiej jednak jest używać rozszerzonego for (for-each). Jego forma dla dowolnych kolekcji jest następująca:

          for (Typ id : kol) stmt

co oznacza, że w każdym kroku iteracji z kolekcji kol pobierany jest (za pomocą jej iteratora) następny element i podstawiany na zmienną id, która może być następnie użyta w instrukcji stmt.
A jaki jest typ oznaczony w powyższej ramce napisem Typ?

Otóż to zalezy od tego czy używamy kolekcji sparametryzowanych typami czy też kolekcji surowych.

Surowe kolekcje moga zawierać referencje do dowolnych obiektów (ich elementy są formalnie typu Object). Metoda next() iteratorów takich kolekcji ma typ wyniku Object.
W przedstawionym przykładzie listy mamy właśnie do czynienia z taką surową kolekcją. Zarówno iterator, jak i metoda get() zwracają wyniki typu Object.

Powinniśmy więc napisać:
for (Object elt : list) System.out.println(elt);
Zauważmy, że przekazanie metodzie println argumentu typu Object powoduje wyprowadzenie napisu zwróconego przez metodę toString() z klasy argumentu. Nie mieliśmy więc kłopotu z faktycznym typem (którym był String).
Jednak gdyby w powyższym przykładzie chcieć wywołać na rzecz zmiennej elt np. metodę length() z klasy String, to kompilator zgłosiłby błąd (statyczna ścisła kontrola typów: istotnie w klasie Object - a takiego typu jest elt - nie ma metody length()!). Musielibyśmy więc dokonywać referencyjnej konwersji zawężającej (mówiliśmy o nich krótko w wykładzie "Klasy i Obiekty" przy okazji wprowadzenia do dziedziczenia):

// Wypisuje długości napisów z kolekcji list
for (Object elt : list) System.out.println( ((String) elt).length());

Użycie kolekcji sparametryzowanych polega na podaniu typu jej elementów w nawiasach kątowych np.

    ArrayList<String> = new ArrayList<String>();

Zapewnia to, że do takiej kolekcji nie będzie można dodać elementu innego typu niż String, a także, że wszelkie metody zwracające elementy tej kolekcji (m.in. get() oraz next() iteratora) będą miały typ wyniku String.
W tym przypadku typ zmiennej w rozszerzonym for może być String i wobec tego możemy pisać tak:
ArrayList<String> list = new ArrayList<String>();
// ....
// Wypisuje długości napisów z kolekcji list
for (String elt : list) System.out.println( elt.length());
O tzw. generics i parametryzacji klas więcej dowiemy się z dalszych wykładów o programowaniu obiektowym, a teraz - intuicyjnie stosując parametry typów  - wróćmy  do głównego nurtu dyskusji - zapoznawania się z wybranymi kolekcjami Javy.


Zauważmy, że w pliku firm dwa razy powtórzono nazwę Sun. Co zrobić, jeśli chcemy mieć wynikowy zestaw firm bez powtórzeń nazw?
Oczywiście, można własnoręcznie oprogramować sprawdzanie elementów zestawu i usuwać z niego duplikaty.
Zbiór jest kolekcją, reprezentującą zestaw niepowtarzających się elementów
Ale po co, jeśli istnieje prostszy sposób - zastosowanie kolekcji typu zbiór.



Możemy np. użyć konkretnej klasy realizującej koncepcję zbioru - klasy HashSet.
class Intro2 {

  public static void main(String args[]) throws IOException {
    Scanner scan = new Scanner(new File("firms.txt"));
    // Utworzenie obiektu klasy HashSet z parametrem typu <String>
    HashSet<String> set = new HashSet<String>();
    while (scan.hasNextLine()) {
      String firm = scan.nextLine();
      // dodanie kolejnego elementu do zbioru
      set.add(firm);
    }
    // wyprowadzenie zawartości zbioru
    for (String elt : set) System.out.println(elt);
  }
  
}
wynik będzie następujący (już bez duplikatów):
IBM
Sun
Apple

Zwróćmy uwagę, że - ogólnie:
Co zrobić, jeśli od naszego programu wymagane jest wyprowadzenie uporządkowanego zestawu firm np. w alfabetycznym porządku ich nazw?

Możemy zastosować kolekcję stanowiącą zbiór uporządkowany. W zbiorze uporządkowanym kolejność przeglądania jego elementów za pomocą iteratora jest określona (np. w rosnącym porządku alfabetycznym nazw firm, będących elementami zbioru). Konkretną realizacją zbioru uporządkowanego jest w Javie klasa TreeSet.

Zatem następujący program:
import java.util.*;
import java.io.*;

class Intro3 {

  public static void main(String args[]) throws IOException {
    Scanner scan = new Scanner(new File("firms.txt"));

    TreeSet<String> set = new TreeSet<String>();
    while (scan.hasNextLine()){
      set.add(scan.nextLine());
    }

    for (String elt : set) System.out.println(elt);
  }
  
}
wyprowadzi nazwy firm, pobrane z pliku, w porządku rosnącym i bez duplikatów:
Apple
IBM
Sun

TreeSet zapewnia uporządkowanie elementów kolekcji, ale usuwa duplikaty. Co zrobić, jeśli chcemy duplikaty zachować i posortować kolekcję? Zachowanie duplikatów zapewnia lista (np. ArrayList).
Możemy wobec niej zastosowac gotowy algorytm sortowania zapisany w postaci statycznej metody klasy Collections (klasa ta zawiera metody realziujące rózne algorytmy działania na kolekcjach).
Poniższy program:
import java.util.*;
import java.io.*;

class Intro4 {

  public static void main(String args[]) throws IOException {
    Scanner scan = new Scanner(new File("firms.txt"));
    ArrayList<String> list = new ArrayList<String>();
    while (scan.hasNextLine()){
      list.add(scan.nextLine());
    }
    Collections.sort(list);
    int i= 0;
    for (String firm : list) {
      System.out.println(++i + ": " + firm);
    }
  }

}
wyprowadzi firmy w rosnącym alfabetycznym  porządku ich nazw:
1: Apple
2: IBM
3: Sun
4: Sun








Wyobraźmy sobie dalej, że w pliku znajdują się  nazwy i adresy firm. Nasz program po wczytaniu pliku ma za zadanie dostarczenie prostego interfejsu wyszukiwania adresu dla podanej nazwy firmy. Jak to zrobić?

Tablica asocjacyjna jest zestawem elementów, do których zapewniono swobodny, bezpośredni (czyli bez konieczności przeglądania elementów zestawu) dostęp za pomocą kluczy. Można sobie wyobrażać, że jest to uogólnienie zwykłej tablicy. W zwykłej tablicy kluczami są indeksy całkowite. W tablicy asocjacyjnej kluczami mogą być dowolne obiekty, Efektywne realizacje tablic asocjacyjnych opierają się na odpowiedniej implementacji  słowników (odwzorowujących klucze w odpowiadające im wartości). W Javie słownikowe implementacje tablic asocjacyjnych nazywane są słowem map, pochodzącym od terminu mapping , oznaczającego  jednoznaczne odwzorowanie (w tym przypadku zbioru kluczy w zbiór wartości). Również po polsku krótko będziemy nazywać tablice asocjacyjne mapami.
Chwila zastanowienia prowadzi do wniosku, że proste sposoby rozwiązania tego problemu (np. prowadzenie dwóch list - nazw i adresów - i liniowe wyszukiwanie nazwy na liście nazw po to by otrzymać pozycję adresu na liście adresów) są bardzo nieefektywne. No i wymagają od nas pisania kodu. Tym więcej (trudniejszego) programowania czekałoby nas, gdybyśmy "od podstaw" próbowali samodzielnie oprogramować bardziej efektywne podejścia do odnajdywania adresów po nazwach (czyli bardziej efektywnie implementować abstrakcyjną strukturę danych jaką jest tablica asocjacyjna).

Na szczęście w Javie mamy odpowiednie klasy, które efektywnie wykonują za nas całą pracę. Należy do nich klasa HashMap, która reprezentuje zestaw par klucz-wartość (ściślej: jednoznaczne odwzorowanie klucze->wartości) i umożliwia - za pomocą metody put(klucz, wartość) dodawanie pary (czyli wartości "pod" podanym kluczem) oraz - za pomocą metody get(klucz) uzyskiwanie wartości, związanej z (znajdującej się "pod") podanym kluczem.

Zatem jeśli postac pliku wejściowego jest następująca:
nazwa_firmy1
adres
nazwa_firmy2
adres
...
nazwa_firmyN
adres

to problem wyszukiwania adresów dla firm podawanych w dialogach wejściowych można oprogramować w następujący sposób:
import java.util.*;
import java.io.*;
import javax.swing.*;

class Intro5 {

  public static void main(String args[]) throws IOException {

    // mapa odwzorowań : nazwa -> adres
    // argumenty typu są dwa, bo dla klucza (nazwy) i dla wartości (adresu)
    HashMap<String, String> map = new HashMap<String, String>();

    // Wczytywanie danych
    Scanner scan = new Scanner(new File("firmsAddr.txt"));
    
    String firmName;
    String address;
    
    while (scan.hasNextLine()) {
      firmName = scan.nextLine();
      address = scan.nextLine();

      // nazwa firmy będzie kluczem
      // pod którym w mapie będzie jej  adres

      map.put(firmName, address); // dodanie pary klucz-wartość do mapy
    }

    // Interakcyjna część programu:
    // dla podanej w dialogu nazwy firmy pokazywany jest jej adres
    while ((firmName = JOptionPane.showInputDialog("Nazwa firmy")) != null) {
      address = map.get(firmName);
      if (address == null) address = "Nie ma takiej firmy";
      JOptionPane.showMessageDialog(null, "Firma: " + firmName + '\n'
          + "Adres: " + address);

    }
  }
}
Działanie programu ilustrują rysunki:
2

r

To krótkie wprowadzenie do kolekcji przekonuje nas, że oprócz tablic w Javie mamy do dyspozycji wiele gotowych klas i metod, pozwalających łatwo rozwiązywać problemy związane z reprezentacją w programie bardziej zaawansowanych struktur danych i operowaniem na nich.

W szczególności, widzieliśmy, że:
Już teraz możemy korzystać z tych narzędzi.
Jednak dobre, zgodne z regułami sztuki posługiwanie się kolekcjami wymaga nieco więcej wiedzy o programowaniu obiektowym.


9. Podsumowanie

Zapoznaliśmy z ważną koncepcją reprezentacji zestawów danych w programach.
Tablice są tu elementarnym środkiem działania i wiedza o ich stosowaniu jest niezbędna.
Jednak nie wszystkie zestawy i struktury danych udaje się reprezentować w postaci tablic. W skrótowym, pragmatycznym wprowadzeniu do kolekcji poznaliśmy więc inne struktury i sposoby działania, znacznie ułatwiające programowanie. Pisanie programów w Javie powoli zaczyna być przyjemnością.