
Ce este un tablou în Java? Probabil ți-e mai cunoscut termenul „array” decât „tablou”, dar merită să știi că sunt unul și același lucru. Pe scurt, putem spune că un tablou reprezintă o colecție de variabile care au același tip de date.
Dacă e să ne referim la tablouri în adevăratul sens al cuvântului, ca analogie, imaginează-ți rama tabloului ca fiind chenarul care cuprinde valorile tipului de date folosit.
Sau, mai simplu, hai să ne gândim la o variabilă. Ai citit articolele următoare?
- Ce este o variabilă?
- Cum declari o variabilă în Java?
- Cum inițializezi o variabilă în Java?
- Tipul unei variabile în Java
Până acum, am lucrat cu variabile care stocau o singură valoare. Ei bine, un tablou poate stoca mai multe valori. Aceasta este principala diferență dintre o variabilă și un tablou (array): o variabilă memorează o singură valoare, iar un tablou memorează cel puțin o valoare.
Să fac un cuprins cu subiectele pe care le voi aborda în acest articol? Voi încerca să creez un articol cu subiecte cât mai cuprinzătoare ca să-ți fie clar o dată pentru totdeauna despre rolul și importanța tablourilor în Java.
Cuprins 🔗
- Ce este un tablou?
- Cum se declară un tablou?
- Cum se inițializează un tablou?
- Cum se definește un tablou?
- Cum se accesează un tablou?
- Tipuri de tablouri
- Avantajele utilizării unui tablou
- Limitările unui tablou
Ce este un tablou? 🔗
Așa cum am precizat deja, un tablou sau un array (în limba engleză vei întâlni cuvântul array) este un obiect care poate stoca mai multe valori. Practic și teoretic, un tablou este o variabilă care poate memora mai multe valori.
În același timp? Da, dar nu în același spațiu. Fiecare spațiu are memoria lui.
Un alt lucru important care merită precizat aici este faptul că un tablou poate conține atât tipuri de date primitive (vezi articolul „Tipuri de date primitive în Java”), cât și obiecte, cum ar fi tipul String.
Înainte să mergem mai departe, reține faptul că un tablou stochează mai multe valori, dar aceste valori trebuie să fie neapărat de același tip. De exemplu, dacă vrei să ai un tablou de tipul int, atunci valorile pe care le inițializezi trebuie să fie musai de tipul int. Orice altă valoare a vreunui alt tip de date va produce o eroare la compilare.
Cum se declară un tablou? 🔗
Exact cum se declară și o variabilă, numai că se pun și parantezele drepte […].
Cum declari o variabilă? Scrii tipul de date, apoi numele variabilei.
Dacă numele variabilei conține mai multe cuvinte, atunci primul cuvânt începe cu literă mică, iar celelalte încep cu literă mare. Și încă ceva: cuvintele trebuie să fie legate. Această metodă se numește camelCase.
Un tablou se declară începând tot cu tipul de date, apoi se pun parantezele drepte […] (fără nimic între paranteze), iar după aceea se trece numele tabloului.
Exemplu: tipDeDate[] numeTablou;
Dacă ai folosit în trecut limbajele C/C++, poate îți amintești că acele paranteze drepte se pun după numele tabloului. Ei bine, în Java este o regulă să pui parantezele drepte imediat după tipul de date, deși nu-i greșit dacă folosești modul de scriere din C/C++.
Hai să ne uităm la câteva exemple de tablouri declarate în Java:
int[] tablouInt; double[] tablouDouble; String[] tablouString;
Pentru ca tablourile declarate să fie utile, trebuie inițializate, adică trebuie să le fie atribuite niște valori. Hai să le inițializăm!
Cum se inițializează un tablou? 🔗
Este asemănător cu inițializarea unei variabile, în sensul că valoarea se pune după semnul de atribuire (vezi articolul „Operatorul de atribuire (=)”), doar că în cadrul tablourilor se pot pune mai multe valori. Și încă ceva: valorile se pun între acolade.
Să ne uităm la exemplul următor:
int[] tablouInt = {1, 2, 3, 4, 5};
double[] tablouDouble = {1.1, 2.2, 3.3, 4.4, 5.5};
String[] tablouString = {"tablou1", "tablou2", "tablou2"};
Fiecărui tablou i-am atribuit valori corespunzătoare tipului de date aferent. Când pui mai multe valori, acestea trebuie să le separi cu ajutorul virgulei.
La variabile, puteam să declar prima dată variabila, apoi pe alt rând să fac inițializarea. Ei bine, la tablouri nu am voie să fac inițializarea totală pe alt rând. Există o excepție doar în cazul în care inițializez obiectul.
Un alt mod de a inițializa un tablou este prin folosirea unui obiect:
int tablouInt = new int[5];
Exemplul anterior este bun, am inițializat un tablou cu 5 elemente, numai că aceste 5 elemente au valoarea 0, adică o valoare predefinită (implicită), nu una definită.
Dacă vrem să dăm un exemplu 100%, atunci putem inițializa fiecare element al tabloului. Acel număr trecut între paranteze drepte se numește index.
Hai să inițializez fiecare index (un index pornește de la 0, deci vom avea indecșii 0, 1, 2, 3, 4, în total 5, adică 5 elemente):
int[] tablouInt = new int[5]; tablouInt[0] = 1; tablouInt[1] = 2; tablouInt[2] = 3; tablouInt[3] = 4; tablouInt[4] = 5;
Am putea spune că în exemplele anterioare am definit un tablou, adică l-am declarat și l-am inițializat, nu? Bun.
Cum se definește un tablou? 🔗
Dacă știi cum se declară și cum se inițializează un tablou (array), înseamnă că știi și cum se definește.
Ai citit articolele pe care ți le-am recomandat la început? Dacă ai fost atent, ai fi văzut că o variabilă se definește prin declarare și inițializare. La fel de simplu se face definirea și în cazul unui tablou: întâi se declară, apoi se inițializează.
Hai să-ți arăt un exemplu de definire a unor tablouri direct cu atribuirea valorilor, adică direct inițializate:
int[] tablouInt = {1, 2, 3, 4, 5};
String[] tablouString = {"unu", "doi", "trei", "patru", "cinci"};
Un tablou se poate declara și fără a-i atribui o valoare specifică fiecărui index:
int[] tablouInt = new tablouInt[5]; /* tabloul este declarat, dar toate cele 5 elemente au valoarea 0, ca valoare predefinită */ String [] tablouString = new String[5]; /* la fel este și în acest caz, numai că valorile predefinite au valoarea null */
Știi cum se accesează un tablou?
Cum se accesează un tablou? 🔗
Probabil ai văzut tot felul de glume cum că la un programator, numărătoarea începe de la 0. Ei bine, accesarea unui tablou se face începând de la indexul 0.
De exemplu, dacă ne uităm la definirea ultimului tablou, vedem numărul 5 între paranteze drepte, fapt ce înseamnă că avem 5 elemente în tabloul respectiv. Pentru a accesa cele 5 elemente, trebuie să începem numărătoarea de la 0.
Hai să luăm ultimul exemplu și să inițializăm fiecare index:
String [] tablouString = new String[5]; tablouString[0] = "unu"; // elementul 1 tablouString[1] = "doi"; // elementul 2 tablouString[2] = "trei"; // elementul 3 tablouString[3] = "patru"; // elementul 4 tablouString[4] = "cinci"; // elementul 5
Așadar, am inițializat fiecare element al tabloului pornind de la indexul 0, adică elementul numărul 1. Prima variabilă a tabloului tablouString[] este tablouString[0]. E interesantă notația asta, nu-i așa?
Această metodă de a începe indexarea unui tablou de la 0 ca fiind primul element a fost popularizată începând cu limbajul C (anii 70′). În C, tablourile sunt legate de pointeri. În Java, tablourile sunt legate de obiecte.
În FORTRAN, indexarea începea de la 1, dar dacă „săpăm” un pic mai adânc, vedem că în „regiștri” sau blocurile de memorie, primul element avea offset-ul 0 și așa s-a hotărât convenția logică pentru limbajele moderne (cuvântul „modern” fiind un cuvânt cu relevanță în ultimii 50 de ani).
Dacă n-ai chef să te „arunci” pe învățarea limbajelor mai vechi, precum FORTRAN, MATLAB sau R și vrei să „sari” pe limbaje populare cum ar fi Java, JavaScript, C++, Python, atunci vei vedea peste tot că primul element începe de la indexul 0.
Hai să ne mai uităm la un exemplu:
int[] tablouInt = {1, 2, 3, 4, 5};
System.out.println(tablouInt[0]);
// afișează numărul 1
System.out.println(tablouInt[1]);
// afișează numărul 2
System.out.println(tablouInt[2]);
// afișează numărul 3
System.out.println(tablouInt[3]);
// afișează numărul 4
System.out.println(tablouInt[4]);
// afișează numărul 5
Ce se întâmplă dacă afișăm indexul 5 la un tablou cu 5 elemente? Adică al 6-lea element al unui tablou definit cu 5 elemente…
Pur și simplu va da o eroare. Ce eroare? Eroarea „java.lang.ArrayIndexOutOfBoundsException”, eroare ce apare pentru că s-a depășit dimensiunea tabloului. Poate să apară și în cazul în care s-a utilizat un index negativ [-1].
Înainte să închei acest capitol, merită să ne uităm și la instrucțiunea for care afișează toate elementele unui tablou. Cum se face asta?
Definim fain-frumos instrucțiunea for, inițializăm o variabilă de la care începe totul, în cadrul condiției folosim metoda length pentru tablouri (care afișează lungimea elementelor sau câte elemente sunt în acel tablou), iar la final punem o incrementare care să treacă prin toate elementele tabloului. În interiorul instrucțiunii for afișăm fiecare element al tabloului.
Dacă nu știi să folosești instrucțiunea for, îți recomand să citești articolul „Instrucțiunea for în Java”.
int[] tablouInt = {1, 2, 3, 4, 5};
for (int i = 0; i < tablouInt.length; i++) {
System.out.println(tablouInt[i]);
}
Un alt exemplu mai ușor ar fi utilizarea instrucțiunii for-each, o instrucțiune specială pentru tablouri în Java.
int[] tablouInt = {1, 2, 3, 4, 5};
for (int i : tablouInt) {
System.out.println(i);
}
Cu ce este mai specială instrucțiunea for-each?
Folosindu-ne de indexul unui tablou, putem să facem și reinițializări, adică să atribuim noi valori variabilelor. Exemplu:
String[] tablouString = {"unu", "doi", "trei", "patru", "cinci"};
tablouString[0] = "zero";
System.out.println(tablouString[0]);
// afișează "zero"
tablouString[5] = "șase";
for (String s : tablouString) {
System.out.println(s);
}
/*
afișează o eroare
pentru că am adăugat
un element care este
în afara dimensiunii tabloului
*/
System.out.println(tablouString.length);
// 5
Tipuri de tablouri 🔗
Nu e momentul să amintesc toate tipurile de tablouri în Java, așa că prefer să merg pe ideea unui articol de nivel mediu atât ca nivel de complexitate, cât și ca nivel de informații prezentate.
Așadar, tipurile de tablouri pe care le voi aminti sunt următoarele: tablouri unidimensionale, tablouri bidimensionale, tablouri neregulate (jagged arrays) și tablouri multidimensionale.
Începem? Am început deja cu tablourile unidimensionale.
Tablouri unidimensionale 🔗
Toate exemplele de tablouri pe care le-ai văzut până acum sunt unidimensionale, adică au o singură „cutie” de stocare, adică o singură pereche de paranteze drepte și o singură „plasă”, adică o pereche de acolade.
Exemple:
int[] tablouInt = {111, 222, 333};
String[] tablouString = {"one", "two", "three"};
double[] tablouDouble = {1.11, 2.22, 3.33};
Deja recunoști tablourile unidimensionale și ți-e ușor să le declari, inițializezi, definești, accesezi, reinițializezi, afișezi, corect?
Poți chiar să vezi și care este lungimea unui tablou unidimensional folosind funcția length:
int[] tablouLung = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("Lungimea tabloului este: " + tablouLung.length);
// Lungimea tabloului este: 10
Trebuie să știi că tablourile unidimensionale sunt numite și vectori. De vectori sigur ai auzit la matematică sau la fizică (sau la mecanică).
Hai să ne uităm un pic la matrici, adică la tablourile bidimensionale.
Tablouri bidimensionale 🔗
Știi ce este o matrice? Sau hai să nu-ți amintesc de școală (cred că matricile le-am făcut prin clasa a 11-a) și te întreb altceva: cum recunoști o matrice?
O matrice se recunoaște după structura unui tabel care are rânduri și coloane. De exemplu, o matrice pătrată seamănă cu tabelul de la jocul X și 0. Rândurile sunt pe orizontală, iar coloanele pe verticală.
Ei bine, o matrice în Java funcționează pe același considerent.
Ce avem în plus la tablourile bidimensionale față de tablourile unidimensionale? O pereche de paranteze drepte și o altă pereche (sau mai multe) de acolade.
Exemplu ușor:
int[][] tablouMatrice = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
În exemplul anterior, rândul 1 are valorile 1, 2, 3, rândul 2 are valorile 4, 5, 6, iar rândul 3 are valorile 7, 8, 9.
Putem să accesăm fiecare element al matricii, astfel:
// rândul 1 - coloanele 1-2-3 System.out.println(tablouMatrice[0][0]); // 1 System.out.println(tablouMatrice[0][1]); // 2 System.out.println(tablouMatrice[0][2]); // 3 // rândul 2 - coloanele 1-2-3 System.out.println(tablouMatrice[1][0]); // 4 System.out.println(tablouMatrice[1][1]); // 5 System.out.println(tablouMatrice[1][2]); // 6 // rândul 3 - coloanele 1-2-3 System.out.println(tablouMatrice[2][0]); // 7 System.out.println(tablouMatrice[2][1]); // 8 System.out.println(tablouMatrice[2][2]); // 9
Ca exemplu, dacă dorim să accesăm valoarea de pe rândul 1, coloana 3, avem nevoie de următoarea expresie: tablouMatrice[0][2];
- [0] reprezintă rândul 1
- [2] reprezintă coloana a 3-a
Scrie în comentarii ce se accesează dacă folosim următoarele trei expresii:
- tablouMatrice[0][1];
- tablouMatrice[1][2];
- tablouMatrice[2][0];
Mulțumesc!
Tablouri neregulate 🔗
Tablourile neregulate sunt un caz special de tablouri bidimensionale (cu două dimensiuni), adică cu rânduri și coloane, numai că numărul acestora nu este clar definit, fapt pentru care poate să difere numărul de coloane și de rânduri.
La capitolul anterior am dat exemple de matrici pătrate. Acum e timpul să dau exemple de matrici „nepătrate”.
Hai să ne uităm la un exemplu cu 3 rânduri și 4 coloane:
int[][] tablouNeregulat = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
Putem să avem o matrice cu o coloană și 4 rânduri:
int[][] tablou4Rânduri1Coloană = {
{1},
{2},
{3},
{4}
};
Sau cu un singur rând și 10 coloane:
int[][] tablouCuUnSingurRând = {
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
};
De altfel, putem să ne și jucăm un pic și să facem matricea de-a dreptul haotică:
int[][] matriceHaotică = new int[4][];
// tablout cu 4 rânduri
matriceHaotică[0] = new int[1];
// primul rând are 1 coloană
matriceHaotică[1] = new int[2];
// al doilea rând are 2 coloane
matriceHaotică[2] = new int[3];
// al treilea rând are 3 coloane
matriceHaotică[3] = new int[4];
// al patrulea rând are 4 coloane
/*
hai să afișăm
matricea neregulată
*/
for (int i = 0; i < matriceHaotică.length; i++) {
for (int j = 0; j < matriceHaotică[i].length; j++) {
System.out.print(matriceHaotică[i][j] + " ");
}
System.out.println();
}
/*
0
0 0
0 0 0
0 0 0 0
*/
Cum ți se pare exemplul anterior? E tare, nu? Am afișat matricea neregulată. Valorile sunt 0 pentru că elementele au valoarea predefinită de la tipul int.
Când ajungi să lucrezi pe proiecte un pic mai complexe, probabil vei întâlni termenul „jagged array”. Să știi că tablourile neregulate sunt „jagged arrays”.
Tablouri multidimensionale 🔗
Un tablou multidimensional este un tablou care conține mai multe tablouri. E voie ca un tablou să aibă în componența sa alte tablouri? Da.
E ca și cum ai avea un business care stă la baza altor businessuri. De exemplu, compania Alphabet deține companiile Admob, Calico, CapitalG, DeepMind, DoubleClick, Google, Google Fiber, GV, Intrinsic, Isomorphic Labs, Mineral, Nest, Verily, Waymo, Wing, X Development, Waze, YouTube.
Bineînțeles, că în cazul dat avem pe Google care la rândul lui deține alte companii și tot așa…
Hai să ne uităm un pic la un tablou tridimensional:
int[][][] cub = new int[2][3][4]; // 2 planuri, 3 rânduri, 4 coloane cub[0][1][2] = 10; /* am setat primul element de pe primul plan, rândul 2, coloana a 3-a */
Ne putem gândi la tablourile multidimensionale pornind de la dimensiunea 2D. Tablourile 2D sunt cele bidimensionale, care sunt incluse în cele multidimensionale.
Așadar, un tablou multidimensional poate fi 2D, 3D, 4D etc.
Chiar dacă nu voi intra în detalii, merită să-ți spun că funcția Arrays.deepToString(numeTablou) este responsabilă de afișarea tablourilor multidimensionale.
Avantajele utilizării unui tablou 🔗
Este eficient să accesăm o valoare direct printr-un index, din moment ce stocăm mai multe valori în aceeași variabilă.
În funcție de cerință, tablourile sunt foarte utile și ușor de integrat într-un program.
Parcurgerea cu instrucțiunile for și for-each (sau foreach) este simplă și intuitivă, fapt ce aduce un mare plus la prelucrarea datelor.
Datorită faptului că tablourile au un bloc continuu de memorie, memoria cache este și ea una eficientă.
Dacă știi ce dimensiune au datele pe care trebuie să le folosești, dacă știi că ai nevoie de acces rapid la anumite valori, dacă te interesează performanța și eficiența memoriei, atunci trebuie să iei în calcul utilizarea unui tablou.
Limitările unui tablou 🔗
Trebuie să știi că un tablou este limitat. De ce? Un motiv ar fi acela legat de dimensiune, adică nu poate fi redimensionat. Un tablou are o dimensiune fixă.
Astfel, dacă ai nevoie de mai multe elemente, trebuie să declari un alt tablou (array).
Un lucru ce ține de eficiența ta în calitate de programator este să nu risipești memorie. Dacă aloci prea mult spațiu unui tablou, riști să risipești memoria anapoda. Evident, dacă aloci prea puțin spațiu, atunci nu ești eficient pentru că trebuie să creezi un alt tablou la nevoie…
Un alt dezavantaj sau o limitare a tablourilor este faptul că nu poți să elimini (să ștergi) direct un element, ci întotdeauna trebuie să-l rescrii.
Cea mai bună alternativă la tablouri sunt listele (ArrayList), iar pentru seturi mai mari de date, putem lua în calcul LinkedList și colecția HashSet. Uite că avem subiecte interesante care merită abordate pe viitor. E musai să le știi dacă vrei să fii un programator de excepție!
Învață să fii profesionist!