Tablouri cu doi sau mai mulți indici

Tabloul cu N indici este considerat în limbajul Java drept un tablou de referințe la tablouri cu N-1 indici. Vom exemplifica aceasta pentru cazul tabloului cu doi indici (bidimensional) și vom extinde apoi conceptul la tablouri cu mai mult de două dimensiuni.
 

Tablouri cu doi indici

Tablourile bidimensionale din limbajul Java sunt o generalizare a conceptului de matrice,  În matematică, toate liniile unei matrice au același număr de componente, în timp ce în Java numărul de componente poate fi diferit de la o linie la alta (deci liniile "matricei" pot avea lungimi diferite). Tabloul cu doi indici este privit ca un tablou ale cărui componente sunt referințe la tablouri cu câte un singur indice. Putem, deci, să ne imaginăm un "tablou coloană" care conține în fiecare componentă o referință către un "tablou linie". Fiecare din aceste "tablouri linie" are propria sa variabilă length și deci propria sa lungime. În schimb, variabila length a "tabloului coloană" reprezintă lungimea acestui tablou, adică numărul de linii. "Tipul tabloului" este, de fapt, tipul componentelor "liniilor" acestuia.
 

Declararea și inițializarea tablourilor cu doi indici

Tabloul bidimensional poate fi declarat în mod asemănător celui unidimensional, dar punând după numele variabilei două perechi de paranteze drepte. Rămâne valabilă și regula că, dacă perechea de paranteze drepte se pune după numele tipului sau al clasei, ea se aplică tuturor variabilelor din declarația respectivă. Fie, de exemplu, următoarele declarații:
    double u, v[], w[][];
    int[] a, b[];
    char[][] h1, h2;
    String ts[][];
Conform acestor declarații:
    - u este o variabilă simplă de tip double;
    - v este un tablou unidimensional, iar w un tablou bidimensional, ambele cu componente de tip double;
    - a este un tablou unidimensional, iar b un tablou bidimensional, ambele  cu componente de tip int;
    - h1 și h2 sunt tablouri bidimensionale cu componente de tip char;
    - ts este un tablou bidimensional, având drept componente referințe la String.

Inițializarea tablourilor bidimensionale se face, de asemenea, asemănător cu a celor unidimensionale, având însă și unele trăsături specifice. Iată cum putem introduce în declarațiile de mai sus inițializări folosind forma cu acolade sau operatorul new:
    double u=5.3127, v[]={1,25, -3.12, 2,6},
        w[][]={{2.31,-4.15},{0.35,-12.6,-573.2},{10.9}};
    int[] a={3,2,7}, b[]={{43,28,92,-6},{},{-1,17,29}};
    char[][] h1={{'a','*'},{'+','$'}}, h2=new char[2][3], h3=new char[4][];
    String ts[][]={{"abc","defg"},{"AB","CD","EF","GH"}};

Remarcam ca, la forma de inițializare cu acolade, tabloul bidimensional este tratat ca un tablou (acoladele exterioare) care are drept componente alte tablouri (acoladele interioare). Acestea din urmă reprezintă "liniile" tabloului bidimensional și pot avea lungimi diferite, unele din ele putând avea chiar lungimea zero (în acest caz acoladele respective nu conțin nimic), ca în cazul liniei a doua a tabloului b.

La folosirea operatorului  new există urmatoarele posibilități de inițializare:
    a/ se indică atât numărul de linii, cât și numărul de coloane, de ex.   new int[7][4]; în acest caz, se inițializează o matrice, în care toate liniile au același număr de componente. În exemplul nostru, matricea are 7 linii și 4 coloane, toate cele 7x4=28 componente fiind inițializate cu valorile implicite ale tipului respectiv (în cazul de față zero);
    b/ se indică numai numărul de linii, de exemplu: new int[7][]. În acest caz se inițializeaza, de fapt, numai "vectorul coloană" care contine referințe (deocamdata nule) către linii care, deocamdată, nu există, urmând sa fie atribuite ulterior.

În figura 1 este reprezentată matricea w dupa inițializare.

- Figura 1 -

Se observă că variabila w conține numai o referință către un tablou cu trei componente care, la rândul lor, conțin referințe către trei tablouri care conțin cele trei linii ale tabloului bidimensional indicat de variabila w. In aceasta situație, cele trei "linii" ale tabloului bidimensional pot să fie situate în locuri diferite din memorie, fără a mai forma o zonă compactă. În schimb, fiecare "linie" este, în acest caz, un tablou compact, ale cărui componente sunt valori primitive de tip double.

Să considerăm acum tabloul
    String ts[][]={{"abc","defg"},{"AB","CD","EF","GH"}};
Acest tablou este reprezentat in figura 2.

- Figura 2 -

Componentele tabloului sunt aici, de fapt, referințe la obiecte din clasa String. La nivel conceptual însă, noi privim acest tablou ca și când ar avea drept componente însăși aceste obiecte. Pentru a le distinge mai ușor, în figura 2 obiectele din clasa String au fost trasate cu culoare albastră.

Inițializările de mai sus sunt testate în programul din fișierul Tab2.java.

Operații cu tablouri bidimensionale

Numărul de componente

Știm că fiecărui tablou unidimensional îi este asociată o variabilă length,  care are ca valoare "lungimea" tabloului, adică numărul de componente ale acestuia. Ce reprezintă variabila length în cazul unui tablou bidimensional? Sa privim din nou figura 1. Întrucat  w.length este numărul de componente din tabloul unidimensional referit de variabila w, înseamnă că el este egal cu numărul de linii al tabloului bidimensional. În schimb, numărul de componente din linia referită de componenta w[i] a tabloului w este dat de variabila  w[i].length. De exemplu, numărul de componente din linia de indice 0 este w[0].length. În programul din fisierul Tab2.java se determină astfel numărul de linii și numărul de componente din fiecare linie pentru fiecare din tablourile bidimensionale inițializate în programul respectiv.

Referirea la componentele tabloului

Pentru a indica o anumită componentă a unui tablou bidimensional, se folosește variabila referință la tablou, însoțită de indicii componentei respective. De exemplu, w[i][j] este componenta situata în linia i și coloana j a tabloului referit de variabila w. Indicii pot fi literali întregi, variabile sau expresii de tip întreg, de exemplu  w[2*k-1][j-3]. Referirile la componentele de tablou au fost folosite, de exemplu, în programul din fișierul Tab2.java   la afișarea tablourilor. În același program se poate urmări și cum sunt afișate componentele tablourilor, în situația când liniile acestor tablouri au lungimi diferite.

Componentele de tablou astfel referite pot fi folosite în orice expresii din program, la fel ca variabilele simple. Iată doua exemple de instrucțiuni, în care se folosesc componentele tabloului bidimensional w:
    t=2*w[1][0]*Math.cos(3.5*w[1][1]-0.23);
    w[j][k+1]=2*w[j-1][k]-w[j][k-1];
Bineînțeles, trebuie avut grijă ca indicii să nu iasă din domeniile admise pentru fiecare din ei deoarece, în caz contrar, vor apare excepții la executarea programului.

Referirea la tablou și la liniile tabloului

Variabila referință la tablou, neînsoțită de indici, este o referire la întregul tablou. Dacă folosim însa variabila referință la un tablou bidimensional însoțită de un singur indice, aceasta este o referință la o linie a tabloului respectiv. De exemplu, în cazul tabloului bidimensional w, w[i] este o referință la linia i a tabloului indicat de variabila w. Variabilele w și w[i] pot fi folosite și ele în diferite expresii. Astfel s-a procedat mai sus, în expresiile  w.length si w[i].length. Să urmărim acum un exemplu, în care se ilustrează folosirea acestor referințe în instrucțiuni de atribuire.

Să urmarim programul din fișierul Tab2a.java, făcând și schemele tablourilor din memorie in diferite etape de execuție a acestuia. Se fac mai intâi următoarele declarații cu inițializări de tablouri:
    int a[][]={{-5,12,52},{8,-4},{},{105}}, b[]={31,-3,17,24}

Rezultatul aceztor initializari este reprezentat in figura 3.
 
 

- Figura 3-

Se fac apoi urmatoarele atribuiri:
    a[0][2]=2*b[1]-a[1][1];
    a[2]=new int[3];
    a[2][0]=b[3];
    a[2][2]=a[3][0];
    a[3]=b;
    b=a[1];
    b[1]=88;

Situatia creată în memorie după această secvență de atribuiri este reprezentată in figura 4.

- Figura 4 -

În aceasta figură, pentru a fi urmărite mai ușor, modificarile au fost făcute cu culoare roșie. A fost creat, prin operatorul new, un nou tablou cu trei elemente, care a devenit noua linie a[2]. S-au atribuit valori pentru a[2][0] si a[2][2], iar componenta a[2][1] a rămas la valoarea implicită 0 cu care a fost inițializată. Fosta linie a[3], formată numai dintr-o singură componentă cu valoarea 105, a rămas fără referință și va fi eliminată de către colectorul de reziduuri de memorie. Noua linie a[3] este acum fostul tablou b, iar tabloul unidimensional indicat de variabila referinta b este acum acelasi cu linia a[1]. Toate aceste modificari pot fi verificate executând programul din fișierul Tab2a.java .

Conversii de tip la tablouri cu doi indici

Regulile aplicate pentru conversii la tablourile bidimensionale sunt aceleași cu cele pentru tablouri unidimensionale. Să urmărim următorul exemplu, care este testat în programul din fișierul Tab2b.java. Fie declarațiile:
    Object ob1, ob2, tob2[][];
    int ti1[][]={{0,1},{10,11,12,13},{20,21,22}},ti2[][];
    String ts[][]={{"aa","ab"},{"ba","bb","bc"},{"ca","cb"}},ts1[][];

Cu aceste declarații, următoarele atribuiri
    ob1=ti1; ob2=ts;
sunt corecte, deoarece clasele ti1[][] și ts[][] sunt descendente ale clasei  Object. În schimb, atribuirile
    ti2=ob1; ts1=ob2;
nu sunt corecte, deoarece se fac în sens "descendent". Știind, totusi, că variabilele ob1 și ob2 conțin efectiv referințe către obiecte din clasele int[][] și respectiv String[][], putem face conversii explicite sub forma:
    ti2=(int[][])ob1; ts1=(String[][])ob2;

Numărul de linii al tabloului indicat de variabila referință ob1 este ((int[][])ob1).length, iar numarul de componente din linia i a aceluiasi tablou este ((int[][])ob1)[i].length. De asemenea, componenta situată în linia i și coloana j este((int[][])ob1)[i][j].

Să încercăm acum atribuirea   ti2=(int[][])ob2; În acest caz, compilatorul nu poate sesiza eroarea, deoarece în momentul compilării nu are informație despre adevarata clasa căreia îi aparține obiectul indicat de variabila  ob2. În schimb, la execuție se constată că obiectul indicat de  ob2 aparține clasei String[][], în timp ce variabila ti1 apartine clasei  int[][], așa că se va genera excepția  ClassCastException.

Tablouri cu mai mulți indici

Modul de tratare al tablourilor cu doi indici poate fi extins și la tablouri cu mai mulți indici (tablouri multidimensionale). Se are în vedere ca tabloul cu  N indici conține referințe la tablouri cu N-1 indici. De exemplu un tablou cu trei indici (tridimensional) poate fi considerat că conține mai multe "pagini", iar fiecare "pagină" este un tablou cu doi indici (bidimensional). În acest caz, primii doi indici specifica linia și, respeectiv, coloana, iar al treilea indice specifica "pagina" în care se găsește o anumită componentă. Un tablou cu patru indici poate fi asemănat cu un set de "volume", în care fiecare "volum" este un tablou cu trei indici, iar cel de al patrulea indice specifica numarul "volumului" din acest set. Putem sa ne imaginam astfel si semnificații ale unor tablouri cu mai mult de patru indici. Totuși, în practică, cele mai frecvent folosite tablouri au cel mult trei indici.

Tablouri eterogene

Prin definiție, componentele unui tablou trebuie să fie toate de același tip. În programarea orientată pe obiecte, această restricție a fost "relaxată", în sensul că un tablou poate avea drept componente și obiecte aparținând claselor descendente din clasa de bază. Faptul că clasa Object este superclasă a oricărei alte clase din limbajul Java, inclusiv a claselor de tablouri, permite să se creeze tablouri eterogene, adică tablouri cu componente care aparțin unor clase foarte diferite. Acestea pot fi structuri complicate de date, realizate pornind de la structura de "tablou de obiecte". Să consideram, de exemplu, structura de date din figura 5.

- Figura 5 -

În programul din fișierul TabEterogen.javase construiește această structură de date eterogenă și se afișează unele dintre componentele ei.



© Copyright 2000 - Severin BUMBARU, Universitatea "Dunărea de Jos" din Galați