Image
22.6.2016 0 Comments

Java pod lupou I. /7.časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Polia

Polia v Jave sú samostatnou kapitolou. Na jednej strane ich nemožno zaradiť medzi primitívne typy, pretože už svojou podstatou patria medzi agregované údajové typy. Na druhej strane nejde ani o plnohodnotné objekty, tak ako o nich bola reč v krátkom úvode do OOP, predovšetkým preto, lebo polia nie sú tvorené na základe vopred zadanej šablóny (ktorej sa hovorí trieda).

Skôr než sa dostaneme k opisu polí v Jave, zopakujme pre menej zdatných, čo je vôbec to pole. Takže najprv učená definícia: pole je postupnosť prvkov rovnakého typu. Dôležité je slovko „rovnakého“. Typickou situáciou, na ktorej možno ukázať opodstatnenosť existencie polí, je nutnosť vykonať nejakú operáciu na netriviálnom množstve údajov. Mohli by sme síce pre každý údaj vytvoriť samostatnú premennú, ale jednak by sme museli zaradom pre tieto premenné vymýšľať mená (a každé iné!), jednak by sme zamýšľanú operáciu museli do programu zadávať znova a znova, hoci zakaždým nad inou premennou. Čo je, pochopiteľne, neúnosné a vo väčšine prípadov prakticky nerealizovateľné riešenie.

Zavedenie poľa oba problémy vyrieši šmahom ruky. Prvky poľa sú zastrešené pod jedným názvom – názvom poľa – a prístup k nim realizujeme prostredníctvom tzv. indexu (ktorým je v Jave celé číslo). No a generovať postupne celé čísla v požadovanom rozsahu vieme – použijeme cyklus, teda napríklad príkaz for. V tele cyklu vyjadríme požadovanú operáciu nad i-tym prvkom poľa a premennú i jednoducho necháme prebehnúť cez všetky indexy poľa.

Vytvorenie poľa

Skôr než pole začneme používať, treba ho nejakým spôsobom vytvoriť. Podobne ako pri primitívnych typoch musíme najprv deklarovať premennú, pomocou ktorej budeme na pole odkazovať. Čitateľ si isto spomenie, že v deklarácii premennej je nevyhnutné zadať okrem názvu aj jej typ. Ten pri poliach vytvoríme jednoducho: pridaním hranatých zátvoriek [] za niektorý z primitívnych typov, ktorý sme si vybrali ako typ prvkov poľa. Ak chceme napríklad pole celých čísel, bude deklarácia vyzerať takto:

int[] ai;

Od tejto chvíle je ai premennou typu „pole prvkov typu int“. Nanešťastie to nie je všetko. Polia v Jave totiž na rozdiel hoci od C++ nikdy nie sú statické a vždy ich treba vytvoriť dynamicky, za behu programu. V jednej z prvých častí seriálu sme hovorili o rozdiele medzi primitívnymi typmi a  referenčnými typmi. Polia ležia niekde na pomedzí, ale bližšie majú k  referenčným typom; dôležité v tomto momente je, že premenná typu pole (teda aj naša premenná ai) obsahuje v skutočnosti len odkaz, referenciu na skutočný objekt poľa. A tento objekt sme zatiaľ nevytvorili. Premenná ai zatiaľ obsahuje len špeciálnu hodnotu null, informujúcu o tom, že ai neukazuje nikam.

Samotný objekt, obsahujúci jednotlivé prvky poľa, môžeme vytvoriť dvojakým spôsobom. Prvý je založený na jednoduchom vymenovaní hodnôt všetkých prvkov; tieto hodnoty oddelíme čiarkami, uzavrieme do zložených zátvoriek a doplníme do deklarácie za operátor =:

int[] ai = { 11, 22, 33, 44 };

Týmto riadkom sme naraz deklarovali premennú ai typu „pole celých čísel“, vytvorili objekt „pole štyroch celých čísel“ s uvedenými hodnotami a do premennej ai sme uložili odkaz na tento novovytvorený objekt. Za povšimnutie stojí, že premenná ai nijako vopred nevie, aký bude počet prvkov poľa, na ktoré bude ukazovať. To je veľmi pozitívna vlastnosť, ktorá programátorovi ušetrí kôpku starostí.

Druhý spôsob vytvorenia objektu poľa je blízky C++. Použijeme kľúčové slovo new, za ktoré doplníme typ poľa podľa uvedeného vzoru, tentoraz však s doplneným počtom prvkov medzi hranatými zátvorkami. Príklad:

int[] ai = new int[4];

Takto vytvorené pole nie je však nijako inicializované a všetky jeho prvky obsahujú implicitné hodnoty, teda nuly. Ak chceme dostať do poľa nejaké rozumné údaje, spravíme to buď v cykle:

for (int i = 0; i < 4; i++)
  ai[i] = i * 11;

alebo použijeme nasledujúci zápis, ktorý spája vytvorenie pomocou new s inicializáciou:

int[] ai = new int[] { 11, 22, 33, 44 };

Tentoraz počet prvkov v hranatých zátvorkách nesmieme uviesť. Na pohľad sa celá vec zdá komplikovaná, ale pravidlo je jednoduché: ak zadáme zoznam inicializačných hodnôt, neuvádzame explicitne počet prvkov (ten je zrejmý zo zadaného zoznamu) a naopak.

A teraz si predstavme inú situáciu. Máme deklarovanú premennú typu pole (teda napríklad premennú ai z nášho príkladu) a chceme jej priradiť odkaz na iné pole, hoci aj s inou dĺžkou. V Jave nijaký problém, zapíšeme buď:

ai = new int[3];

a pole naplníme v cykle, alebo použijeme tvar:

ai = new int[] { 111, 222, 333 };

Pôvodné štvorprvkové pole je od tejto chvíle stratené, zabudnuté a odsúdené na neľútostný koniec v pažeráku garbage-collectora.

Prístup k prvkom poľa

Keď už máme pole vytvorené, môžeme s jeho prvkami pracovať. i-ty prvok poľa ai sprístupníme zápisom:

pi[i]

teda za názov premennej, ktorá odkazuje na dané pole, dáme do hranatých zátvoriek príslušný index. Indexy poľa, ktoré má N prvkov, sú vždy v rozsahu 0 až N–1. Ak by sme zadali index záporný alebo väčší, než je dĺžka poľa, dôjde počas behu programu k výnimke. Hľa, aký rozdiel oproti C++! Tam sme mohli prepisovať pamäť, ako sa nám zapáčilo. Už je teraz jasnejšie tvrdenie, že Java je bezpečnejšia ako C++?

Konštanta alebo premenná použitá ako index musí byť typu int. Môžeme, samozrejme, použiť aj premenné typu byte, short a char, pretože tie sa automaticky konvertujú na typ int; nie je však dovolené indexovať polia pomocou premenných (a konštánt) typu long.

Ukážme si teraz krátky príklad, v ktorom vytvoríme pole, naplníme ho náhodnými hodnotami a potom spočítame súčet prvkov poľa a ich aritmetický priemer:

int[] a = new int[20];
for (int i = 0; i < a.length; i++)
{
  a[i] = (int)(Math.random() * 100);
  System.out.print(a[i] + " ");
}
int sum = 0;
for (int i = 0; i < a.length; i++)
  sum += a[i];
double avg = (double)sum / a.length;
System.out.println("\nSum = " + sum);
System.out.println("Avg = " + avg);

Na generovanie náhodných čísel používame metódu Math.random(), ktorá vracia pseudonáhodné číslo typu double z intervalu <0; 1). To vynásobíme číslom 100 a výsledok pretypujeme na int, takže každý prvok poľa bude náhodným číslom z intervalu 0 až 99.

V príklade sa vyskytuje ešte jeden nový prvok, a to výraz a.length. Polia v Jave sú objektmi a objekty, ako vieme, obsahujú atribúty. V každom poli takto máme k dispozícii konštantný atribút length, ktorý udáva počet prvkov poľa. Čo viac si môžeme želať? Ak posielame pole ako argument nejakej funkcii, netreba spolu s ním posielať jeho dĺžku, pretože tú si funkcia zistí sama z poslaného poľa!

Viacrozmerné polia

Prvkami polí nemusia byť len premenné primitívneho typu, rovnako dobre môžeme deklarovať polia objektov. A keď je každé pole samo osebe takým „skoroobjektom“, prečo by sme nemohli deklarovať pole, ktorého prvkami budú opäť polia?

Pojem viacrozmerné polia nie je celkom presný, rovnako ako nebol celkom presný v C++. Viacrozmerné polia nájdeme v Pascale, ale v C++ alebo v Jave ide v skutočnosti o polia, ktorých prvky sú takisto polia. A prvkami týchto polí môžu byť opäť polia a tak ďalej, do hĺbky, ktorú potrebujeme.

Ukážme si teraz na príklade, ako by sme deklarovali „dvojrozmerné“ pole celých čísel. Potrebujeme deklarovať pole prvkov typu „pole celých čísel“. Tento typ už poznáme, je to int[]. A poznáme aj pravidlo, ako vytvoriť typ poľa prvkov nejakého typu – pridaním hranatých zátvoriek. Tak dostaneme výsledný typ int[][]. Príklad deklarácie:

int[][] aai;

Takto deklarovaná premenná aai opäť neukazuje nikam a obsahuje len hodnotu null. Jediné, čo o nej môžeme povedať, je, že ak bude na niečo odkazovať, tak to niečo bude pole polí celých čísel. Ak ju chceme spolu s deklaráciou inicializovať, použijeme napríklad takýto zápis:

int[][] aai = { { 1, 2 }, { 3, 4 } };

(Všimnite si rekurzívnosť inicializačného zoznamu. Ten je tvorený postupnosťou inicializačných hodnôt oddelených čiarkou a uzavretých do zložených zátvoriek. Ak inicializačná hodnota inicializuje vnorené pole, bude sama osebe podobným zoznamom.)

Teraz premenná aai odkazuje na dvojprvkové pole, ktorého každý prvok je opäť dvojprvkové pole celých čísel. Prvé pole, aai[0], obsahuje prvky aai[0][0] a aai[0][1], druhé pole, aai[1], obsahuje zase prvky aai[1][0] a aai[1][1]. V pamäti existujú teda tri polia: prvé pole je to, na ktoré odkazuje premenná aai. Toto pole má dva prvky, ktorými sú dve premenné aai[0] a aai[1] odkazujúce každá na svoje samostatné dvojprvkové pole.

Premennú aai môžeme inicializovať aj takýmto spôsobom:

int[][] aai = { null, null };

Tentoraz sa v pamäti vytvorilo iba jediné pole, na ktoré odkazuje premenná aai. Oba jeho prvky majú hodnotu null, a teda neukazujú nikam. Aby sme ich prinútili ukazovať na nejaké reálne objekty polí, musíme ich inicializovať explicitne:

aai[0] = new int[] { 1, 2 };
aai[1] = new int[] { 3, 4, 5 };

A teraz dávajte dobrý pozor! Zatiaľ čo premenná aai[0] ukazuje podobne ako v predchádzajúcom príklade na dvojprvkové pole obsahujúce hodnoty 1 a 2, premenná aai[1] odkazuje na pole troch prvkov, čísel 3, 4 a 5! Vo výsledku sme teda dostali dvojrozmerné pole, ktorého druhý rozmer nie je pevne daný, ale sa mení podľa indexu prvej úrovne. (Keď sa to tak vezme, podobnú konštrukciu možno vytvoriť aj v C++, ale trochu menej elegantne a možno aj menej prehľadne. A okrem toho javovské runtime prostredie automaticky kontroluje, či sme zadali správne indexy a či sa náhodou nesnažíme pristupovať „mimo“ poľa.) Mimochodom, uvedené nepravidelné pole zvládneme vytvoriť aj jediným riadkom:

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

Ktorý spôsob si vyberieme, závisí od toho, či poznáme inicializačné hodnoty vopred, alebo nie.

Dosiaľ sme ukázali len spôsoby, pomocou ktorých sme najvyššiu úroveň poľa aai inicializovali zoznamom hodnôt. Pochopiteľne, podobné výsledky môžeme dosiahnuť použitím operátora new:

int[][] aai = new int[2][2];

Takto deklarovaná a inicializovaná premenná aai ukazuje na pole dvoch polí, obsahujúcich implicitné hodnoty, teda nuly. Inicializáciu môžeme trochu obmedziť a zapísať:

int[][] aai = new int[2][];

Teraz premenná aai ukazuje na pole, obsahujúce dve premenné typu int[], ale zatiaľ neinicializované, a teda obsahujúce hodnoty null. Explicitne ich inicializujeme napríklad takto:

aai[0] = new int[2];
aai[1] = new int[3];

Jednotlivé prvky polí druhej úrovne budú v takomto prípade obsahovať, samozrejme, nuly.

Celá teória okolo viacrozmerných polí bude zrejme na prvý pohľad vyzerať zložito a zamotane, hlavne pre začiatočníka, ale len málo vecí je takých, aké sa nám zdajú na prvý pohľad. Stačí sa dobre zamyslieť, vziať si ceruzku a papier a pokúsiť sa celú situáciu nakresliť. A takisto si treba sadnúť k počítaču, napísať zopár deklarácií a snažiť sa zisťovať, ktorá premenná má akú hodnotu, čo je definované a čo pri pokuse o výpis vyhodí výnimku.

Zhrňme všetky doteraz uvedené poznatky do niekoľkých bodov:

§ ak v deklarácii uvedieme zoznam inicializačných hodnôt, neuvádzame pre danú úroveň počet prvkov poľa;

§ hociktorý inicializačný zoznam na niektorej vnorenej úrovni môžeme nahradiť hodnotou null, potom bude táto časť poľa neinicializovaná a príslušné podpolia budeme musieť vytvoriť explicitne;

§ ak použijeme na vytvorenie poľa na ľubovoľnej úrovni kľúčové slovo new bez uvedenia inicializačného zoznamu, musíme uviesť počet prvkov tohto poľa;

§ pri vytváraní poľa pomocou new môžeme sprava vynechať ľubovoľný počet rozmerov polí nižších úrovní – tieto polia potom budeme musieť vytvoriť explicitne.

Na dokonalé ozrejmenie týchto pravidiel je tu ešte na záver príklad poľa s tromi rozmermi. Použijeme štyri rozličné spôsoby vytvorenia poľa s použitím kľúčového slova new. Prvý spôsob vytvorí naraz celé pole 3 × 2 × 4 prvkov:

int[][][] aaai = new int[3][2][4];

Jednotlivé prvky budú mať nulové hodnoty.

Druhý spôsob vytvorí naraz prvú a druhú úroveň, tretiu úroveň vytvoríme explicitne a „zubato“:

int[][][] aaai = new int[3][2][];
aaai[0][0] = new int[2];
aaai[0][1] = new int[5];
aaai[1][0] = new int[4];
aaai[1][1] = new int[3];
aaai[2][0] = new int[6];
aaai[2][1] = new int[2];

Polí tretej úrovne je, ako vidíme z príkladu, spolu šesť (3 × 2) a každé bude mať inú dĺžku.

V treťom spôsobe vytvoríme pomocou new len najvyššiu úroveň poľa, druhú a tretiu úroveň vytvoríme explicitne (ale naraz!)

int[][][] aaai = new int[3][][];
aaai[0] = new int[2][4];
aaai[1] = new int[3][3];
aaai[2] = new int[4][2];

Ako vidieť z príkladu, prvé dvojrozmerné podpole bude mať 2 × 4 prvky, druhé 3 × 3 prvky a tretie 4 × 2 prvky. Všetky prvky budú inicializované nulovými hodnotami.

Modifikáciou tretieho spôsobu môžeme vytvoriť ešte štvrtý spôsob, v ktorom by sa druhá a tretia úroveň vytvárali oddelene:

int[][][] aaai = new int[3][][];
aaai[0] = new int[2][];
aaai[0][0] = new int[2];
aaai[0][1] = new int[5];
aaai[1] = new int[3][];
aaai[1][0] = new int[4];
aaai[1][1] = new int[3];
aaai[1][2] = new int[2];
aaai[2] = new int[4][];
aaai[2][0] = new int[6];
aaai[2][1] = new int[2];
aaai[2][2] = new int[5];
aaai[2][3] = new int[3];

Je očividné, že ide v podstate o skombinovanie druhého a tretieho spôsobu. Výsledné pole bude mať (2 + 5) + (4 + 3 + 2) + (6 + 2 + 5 + 3), čo je spolu 32 prvkov.

Špecifiká polí v Jave

Nasledujúci odsek je určený predovšetkým tým čitateľom, ktorí ovládajú jazyk C a nedajbože trpia utkvelou predstavou, že Java je také zjednodušené céčko. Tak predovšetkým v céčku neexistuje samostatný typ pre reťazec znakov. Na prácu so znakovými reťazcami sa používa pole znakov, teda typ char[] a v podstate vzhľadom na známu schizofréniu polí v céčku aj typ char*. V Jave nič takéto neplatí. Hoci je možné vytvoriť pole premenných typu char, bude nám poväčšine nanič, pretože na prácu s reťazcami slúži vstavaný objektový typ String, resp. StringBuffer. Ich súčasťou je množstvo metód, ktoré na prácu s reťazcami treba, takže nemá prakticky nijaký význam snažiť sa tvrdošijne operovať so znakovými poľami. A nehovoriac o tom, že v Jave nie sú reťazce ukončené nulovým znakom.

Druhá skutočnosť je pre ortodoxných céčkarov oveľa priaznivejšia. Pri deklarácii poľa totiž netreba uvádzať hranaté zátvorky len za názov typu, môžeme ich podobne ako v C/C++ doplniť za názov deklarovanej premennej,  dokonca je dovolené oba spôsoby kombinovať. Premennú typu „pole polí celých čísel“ teda môžeme deklarovať až tromi rôznymi spôsobmi:

int[][] aai;
int[] aai[];
int aai[][];

Všetky tri spôsoby sú rovnocenné.

A zopár príkladov

Skôr než tému polí uzavrieme, ukážeme si ešte dva príklady. V prvom z nich vytvoríme zvláštnu konštrukciu trojuholníkovej „matice“ – dvojrozmerné pole, ktorého počet stĺpcov bude postupne rásť v závislosti od poradového čísla riadka matice:

int[][] mat = new int[10][];
for (int i = 0; i < mat.length; i++)
  mat[i] = new int[i+1];
 
for (int i = 0; i < mat.length; i++)
  for (int j = 0; j < mat[i].length; j++)
    mat[i][j] = (int)(Math.random() * 10);
 
for (int i = 0; i < mat.length; i++)
{
  for (int j = 0; j < mat[i].length; j++)
    System.out.print(mat[i][j] + " ");
  System.out.println("");
}

Ponajprv maticu vytvoríme. V cykle for, kde sa dynamicky vytvárajú riadky matice, vidieť, že počet prvkov i-teho riadka má byť i + 1. Skutočne teda vytvoríme konštrukciu v tvare trojuholníka. V nasledujúcej dvojici cyklov for maticu naplníme náhodnými hodnotami a nakoniec tieto hodnoty vypíšeme na obrazovku. Za pozornosť stojí, že všetky cykly, ktoré s premennou mat pracujú, si zistia dĺžku príslušných polí pomocou výrazu mat.length, resp. mat[i].length. Netreba dávať pozor, či cykly pracujú nad celým poľom, netreba zavádzať žiadne konštanty, jednoducho, programátorské selanky :–).

V druhom príklade predvedieme násobenie dvoch matíc A a B, výsledok uložíme do matice C. Z algebry vieme, že násobiť možno iba také dve matice, z ktorých prvá má rovnaký počet stĺpcov ako druhá riadkov a výsledok „zdedí“ počet riadkov prvej matice a počet stĺpcov druhej matice:

int[][] A = new int[3][4];
int[][] B = new int[4][2];
int[][] C = new int[3][2];
 
for (int i = 0; i < A.length; i++)
  for (int j = 0; j < A[i].length; j++)
    A[i][j] = (int)(Math.random() * 10);
 
for (int i = 0; i < B.length; i++)
  for (int j = 0; j < B[i].length; j++)
    B[i][j] = (int)(Math.random() * 10);
 
for (int i = 0; i < C.length; i++)
  for (int j = 0; j < C[i].length; j++)
    for (int k = 0; k < A[i].length; k++)
      C[i][j] += A[i][k] * B[k][j];
 
System.out.println("A:");
for (int i = 0; i < A.length; i++)
{
  for (int j = 0; j < A[i].length; j++)
    System.out.print(A[i][j] + " ");
  System.out.println("");
}
 
System.out.println("\nB:");
for (int i = 0; i < B.length; i++)
{
  for (int j = 0; j < B[i].length; j++)
    System.out.print(B[i][j] + " ");
  System.out.println("");
}
 
System.out.println("\nA.B:");
for (int i = 0; i < C.length; i++)
{
  for (int j = 0; j < C[i].length; j++)
    System.out.print(C[i][j] + " ");
  System.out.println("");
}

Prvé dva cykly for naplnia matice A a B náhodnými číslami od 0 po 9. V treťom cykle počítame súčin oboch matíc a výsledok ukladáme do prvkov matice C. S výhodou využijeme fakt, že jednotlivé prvky sú inicializované nulami. Keby to tak nebolo, treba na vhodné miesto doplniť riadok C[i][j] = 0. Kam, to už ponechávam na čitateľovi. Zvyšok programu sa postará už len o výpis všetkých troch matíc.

Žatva sa skončila…

… a my necháme polia poľami. V budúcej časti prejdeme k triedam a práci s objektmi, dovtedy dovidenia.

 

 


Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Ako funguje sandbox?

08.12.2016 15:36

Každá aplikácia môže pre operačný systém počítača či mobilného zariadenia predstavovať potenciálnu hrozbu, a to aj v prípade, ak neobsahuje žiadne bloky škodlivého kódu. Murphyho zákony neúprosne defi ...

Ako na to

Tipy a triky: Ako na snímku obrazovky na akomkoľvek počítači s Windows?

02.12.2016 00:13

Ak snímky obrazovky robíte často apotrebujete napríklad funkcie na posun stránok alebo snímanie zobrazenia pri vyššom rozlíšení displeja, zrejme používate nejakú špecializovanú aplikáciu. Väčšina použ ...

Ako na to 1

Tipy a triky: Ako aplikácii prednastaviť spúšťanie s administrátorskými právami?

30.11.2016 00:10

Väčšina aspoň trochu skúsenejších používateľov vie, že aj keď máte na operačnom systéme Windows vytvorený administrátorský účet, aplikácie pre bezpečnosť nefungujú vždy splnými administrátorskými práv ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá