Image
22.6.2016 0 Comments

Java pod lupou I. /8.časť

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

Objekty a triedy

Na popud istého čitateľa bude počnúc týmto číslom PC REVUE možné nájsť na webových stránkach PC REVUE v sekcii Programujeme kompletné zdrojové texty preberaných príkladov vo forme, na akú v časopise (žiaľ) nie je miesto, a podľa situácie aj niekoľko príkladov navyše. Toľko úvodná informácia, môžeme sa vrátiť k Jave. V ôsmom pokračovaní budeme hovoriť o deklarácii tried.

Java je výrazne objektovo orientovaný jazyk. Javovský program je tvorený množinou objektov, ktoré medzi sebou komunikujú. Komunikácia sa deje prostredníctvom zasielania správ, ktoré sa v Jave realizuje ako klasické volanie procedurálnych jednotiek, tzv. metód jednotlivých objektov. Každý objekt je tak charakterizovaný jednak množinou svojich členov, ktoré opisujú jeho okamžitý stav, a jednak množinou svojich metód, ktoré opisujú možné správanie objektu. Na zoznam metód možno pohliadať ako na exportovaný zoznam správ, na ktoré je objekt schopný reagovať.

(Ešte poznámka k termínu člen: Použil som takúto formuláciu, pretože preklad pôvodného anglického slova field, teda pole, by mohol kolidovať s poľom ako údajovým typom, ktoré sa pre zmenu v angličtine povie array.)

V Jave v zásade nie je možné vytvárať objekty ad hoc, čírym vymenovaním ich obsahu (existujú anonymné triedy, ale tie nie je možné používať na najvyššej úrovni). Prv než môžeme začať používať nejaký objekt, treba opísať schému, na základe ktorej sa tento objekt vygeneruje. Takejto schéme sa hovorí deklarácia triedy objektu. V pôvodnom chápaní OO prístupu je trieda samostatným metaobjektom, ktorý dokáže generovať nové objekty. Čiastočne je to tak aj v Jave – počas behu programu existuje run-time reprezentácia každej triedy, na základe ktorej bol vytvorený nejaký objekt –, ale koncept triedy je orientovaný viac syntakticky; trieda je teda syntaktická konštrukcia, ktorá opisuje polia a metódy objektov. Výhoda existencie tried je zrejmá: na základe jednej šablóny možno jednoducho vytvárať objekty s rovnakou štruktúrou.

Ako teda vlastne vyzerá zdrojový kód javovského programu? Je zvykom zapisovať deklaráciu každej triedy do samostatného súboru s príponou .java. Pri verejných (public) triedach je to dokonca nutnosť a názov každého takéhoto súboru sa musí zhodovať s názvom verejnej triedy v ňom deklarovanej. Pri preklade programu sa súbory .java transformujú na súbory s príponou .class, ktoré obsahujú binárnu reprezentáciu jednotlivých tried. Ešte treba vyriešiť otázku, ako má program začať. Ako sme už spomínali, jedna z tried musí obsahovať statickú metódu main(), ktorú možno vyvolať bez toho, aby existoval konkrétny objekt príslušnej triedy. Táto metóda sa potom musí postarať o vytvorenie zodpovedajúcich objektov (ak je to naším cieľom).

Triedy a ich deklarácia

Deklaráciou triedy zavádzame do programu nový referenčný typ; samotná deklarácia opisuje jeho implementáciu. Triedy možno deklarovať na rôznych úrovniach, podľa toho ich delíme na triedy na najvyššej úrovni (top-level) a na triedy vnorené (nested). Na začiatku sa budeme zaoberať výhradne triedami top-level, vnorené triedy (vnútorné, lokálne či anonymné) prídu na rad až neskôr.

Pre názornosť uvedieme hneď na začiatku kompletnú deklaráciu jednoduchej triedy, na ktorú budeme v ďalšom výklade odkazovať. Triedu nazveme Point, bude totiž predstavovať bod v dvojrozmernom priestore. Taký bod je úplne opísaný svojimi dvoma súradnicami, ktoré budú predstavovať stav objektu. Náš bod bude schopný presunúť sa na určené súradnice a ďalej sa dokáže určitým obmedzeným spôsobom „zobraziť“.

Tu je zdrojový kód triedy Point, umiestnený v súbore Point.java:

public class Point
{
  private int x;    // x-ová súradnica
  private int y;    // y-ová súradnica
 
  // konštruktory
  Point()
  {
    this(0, 0);
  }
 
  Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
 
  // move() – presun na nové súradnice
  public void move(int x, int y)
  {
    this.x = x;
    this.y = y;
  }
 
  // getX() – vrátenie x-ovej súradnice
  public int getX()
  {
    return x;
  }
 
  // getY() – vrátenie y-ovej súradnice
  public int getY()
  {
    return y;
  }
 
  // show() – „zobrazenie“ bodu
  public void show()
  {
    System.out.println("Point at [" + x + ", " + y + "]");
  }
}

Všeobecný zápis deklarácie triedy vyzerá s určitými zjednodušeniami takto

modifikátory class MenoTriedy
{
    telo
}

Pre meno triedy platia rovnaké pravidlá ako pre identifikátor. Je zvykom začínať meno triedy (a pokiaľ je toto meno viacslovným spojením, aj jednotlivé zložky mena) veľkým písmenom, ako napr. LineBuffer, TextFrame a i.

Pred kľúčovým slovom class sa v deklarácii vyskytujú tzv. modifikátory triedy, ktoré určitým spôsobom upravujú vlastnosti novo deklarovanej triedy. Ako modifikátory triedy možno použiť kľúčové slová public, protected, private, abstract, static či final. Tri z týchto modifikátorov, menovite protected, privatestatic, možno použiť len pre triedy deklarované v rámci inej triedy. Modifikátory abstractfinal dostanú zmysel v okamihu, keď budeme hovoriť o vzťahu dedičnosti medzi triedami, a konečne modifikátor public robí triedu verejnou, čo však bližšie vysvetlíme až pri rozprávaní o balíkoch.

Telo deklarácie triedy tvoria deklarácie členov triedy a jej metód. Okrem nich možno v triede nájsť ešte deklarácie vnorených tried a rozhraní, ale tie zatiaľ preskočíme.

Deklarácia členov triedy

Prvou časťou deklarácie triedy je deklarácia jej členov, v ktorých bude uložený stav objektu. Deklarácia členov triedy sa nijako výrazne nelíši od deklarácie lokálnych premenných, samozrejme, ak neberieme do úvahy fakt, že členské premenné takto deklarované sú trvalou súčasťou príslušného objektu, zatiaľ čo lokálne premenné zanikajú po opustení tela príslušného bloku kódu.

Všeobecný zápis deklarácie členov triedy vyzerá teda takto:

modifikátory typ zoznam-deklarátorov ;

kde zoznam deklarátorov je čiarkami oddelená postupnosť deklarátorov v tvare

identifikátor

alebo

identifikátor = inicializátor

Ak sa pozrieme do triedy Point, nájdeme tam deklaráciu dvoch členov, xy, ktoré obsahujú x-ovú a y-ovú súradnicu príslušného bodu. Deklarácia člena x vyzerá takto:

private int x;

V tejto deklarácii sa nachádza jediný deklarátor, identifikátor x. Typom člena x bude int. Okrem toho je súčasťou deklarácie jeden modifikátor, kľúčové slovo private, ktoré hovorí o tom, že člen x bude súkromným členom triedy Point a nikto iný k nemu nebude mať prístup. Deklarácia člena y vyzerá podobne.

Obe deklarácie by sme mohli spojiť do jednej:

private int x, y;

V takejto deklarácii je zoznam deklarátorov tvorený dvoma identifikátormi, xy. Dôležité je zapamätať si, že modifikátory a typ sa vzťahujú na všetky identifikátory v zozname deklarátorov.

Ak by sme pociťovali potrebu oba členy inicializovať nejakou hodnotou, stačí deklarácie upraviť na tvar:

private int x = 0;
private int y = 0;

resp.

private int x = 0, y = 0;

Inicializovanie premenných (hocijakých) nulovou hodnotou je však zbytočné, to sa robí automaticky a okrem toho je trieda Point napísaná tak, že pokiaľ pri vytváraní novej inštancie (inštancia triedy  =  objekt vytvorený na základe tejto triedy) neuvedieme počiatočné hodnoty súradníc, implicitne sa budú uvažovať nulové.

V deklarácii jednej triedy sa nesmú vyskytovať dva členy s rovnakým názvom. Je to logické, pretože by pri prístupe k nim dochádzalo k nejednoznačnosti. Je však možné, i keď neveľmi žiaduce, pomenovať člen triedy rovnako ako niektorú z metód triedy.

Keďže jediné, čo odlišuje deklaráciu členských premenných od deklarácie lokálnych premenných, sú modifikátory, zameriame sa teraz na ne. Ako modifikátory členov triedy možno použiť kľúčové slová public, protected, private, static, final, transientvolatile.

Prvé tri modifikátory ovplyvňujú spôsob prístupu k členom. Kľúčovým slovom public sa označujú členy verejné. K takýmto členom môže pristupovať ktokoľvek: samotné metódy objektu, metódy prípadných odvodených objektov, ale aj metódy ľubovoľného iného objektu. Verejné členy treba používať opatrne, pretože v zásade nie je možné zabrániť nekonzistentnej manipulácii s nimi bez toho, aby o tom samotný objekt niečo vedel. Kľúčové slovo private znamená presný opak: takto označené členy sa považujú za súkromné pre daný objekt. Okrem metód objektu k nim nemá prístup nikto, ani metódy odvodených tried. Takéto členy sú úplne konformné s jedným zo základných pilierov OOP, konceptom zapuzdrenia. Jediná možnosť, ako meniť hodnotu súkromných členov, je prostredníctvom metód objektu, a tie sú, samozrejme, úplne pod kontrolou objektu. Konečne posledný modifikátor protected definuje členy ako tzv. chránené; takéto členy sú prístupné na rozdiel od súkromných členov aj odvodeným triedam, nie však metódam ostatných objektov.

Je chybou, ak pri deklarácii člena triedy uvedieme viac ako jeden z modifikátorov public, protected alebo private. Je však možné neuviesť ani jeden z týchto modifikátorov, v takom prípade sa členy správajú ako chránené, s tým rozdielom, že k nim majú prístup metódy všetkých tried nachádzajúcich sa v tom istom balíku. Viac na túto tému neskôr.

Kľúčové slovo static označuje tzv. statické členy. Rozdiel medzi nestatickými a statickými členmi je pomerne zásadný. Zatiaľ čo nestatický člen triedy existuje samostatne v každej kópii objektu, vytvoreného podľa tejto triedy, statický člen existuje pre každú triedu práve jeden. Inak povedané, nestatické členy sú súčasťou každej vytvorenej inštancie triedy a zmena nestatického člena jedného objektu nijako neovplyvňuje hodnotu rovnako pomenovaného člena iného objektu tej istej triedy. Statický člen je, naopak, zdieľaný všetkými objektmi jednej triedy a de facto nie je súčasťou jednotlivých objektov, ale samotnej triedy. Je teda možné s ním pracovať aj vtedy, ak dosiaľ nebola vytvorená nijaká inštancia tejto triedy. Pre nestatické členy sa v angličtine používa aj termín instance variables, statické sú potom class variables. Z oboch pomenovaní je okamžite zrejmé, komu daný člen patrí.

Klasickým príkladom použitia statického člena by mohlo byť počítadlo vytvorených objektov. Každé vytvorenie novej inštancie by spôsobilo inkrementáciu počítadla. Deklarácia člena by mohla vyzerať takto:

public static long counter = 0;

A v tele konštruktora triedy (čo to je, to sa dozvieme o chvíľu) by sa nachádzal riadok:

counter++;

Kompletný príklad takejto triedy nájdete na webovej stránke PC REVUE v sekcii Programujeme.

Ďalší modifikátor final hovorí o tom, že člen takto označený svoju hodnotu nebude meniť a bude sa správať ako pomenovaná konštanta. Je pochopiteľné, že final členy treba inicializovať priamo v deklarácii. Často je výhodné deklarovať finálne členy súčasne ako statické, potom máme možnosť pristupovať k nim bez nutnosti vytvárať nový objekt. Ako príklad možno uviesť takúto deklaráciu:

public class Const
{
  public static final double PI = 3.1415926;
}

Trieda Const obsahuje jediný člen PI, ktorý je statický a zároveň konštantný. V programe by sme ho mohli sprístupniť jednoduchým zápisom Const.PI a používať namiesto literálu 3.1415926.

Modifikátor transient označuje členy, ktoré nie sú súčasťou perzistentného stavu objektu. Inak povedané, keby sme chceli objekt serializovať a uložiť na perzistentné médium (napríklad do súboru na disku), alebo hoci poslať po sieti v rámci distribuovanej aplikácie, tranzientné členy by sa v takomto prípade neukladali – stav objektu by bolo možné rekonštruovať aj bez hodnôt týchto členov.

Posledný členský modifikátor volatile sa používa na označenie členov, ktorých hodnotu treba pri použití člena vždy nanovo čítať z príslušnej oblasti pamäte. Pri multithreadových aplikáciách je bežné, že si jednotlivé thready uchovávajú lokálne kópie určitých premenných, ktoré sa s hlavnou kópiou synchronizujú len v určitých okamihoch. Modifikátor volatile túto synchronizáciu vynucuje pri každom použití príslušnej premennej.

Deklarácia metód triedy

Metódy na rozdiel od členov opisujú správanie objektov, a  preto budú obsahovať kód. Deklarácia metódy vyzerá vo všeobecnosti takto:

modifikátory výsledok názovMetódy ( parametre )
{
    telo
}

Každá metóda má svoj názov, pre ktorý platia známe pravidlá. Podobne ako pri pomenovávaní tried je zvykom začínať jednotlivé slová vo viacslovnom pomenovaní veľkým písmenom, názvy metód sa však obyčajne začínajú malým písmenom, ako napr. moveTo, convertToBinary a pod.

Metóda sa z hľadiska vykonávania programu správa rovnakým spôsobom ako klasická céčkovská funkcia. Možno jej poslať parametre a môže volajúcemu objektu vrátiť nejakú hodnotu. Typ výsledku sa v deklarácii zapisuje pred názov metódy. Zoznam formálnych parametrov metódy uvedieme do okrúhlych zátvoriek za názov metódy. Jednotlivé parametre majú tvar klasickej deklaračnej dvojice typ názov a sú oddelené čiarkou. Názvy jednotlivých parametrov možno používať v tele metódy ako lokálne premenné, ktoré sa naplnia príslušnými hodnotami pri volaní metódy.

Zoberme si ako príklad metódu move() triedy Point. Tu je pre úplnosť jej deklarácia:

public void move(int x, int y)
{
  this.x = x;
  this.y = y;
}

Vidíme, že táto metóda prijíma dva parametre, ktoré sme pomenovali xy a oba sú typu int. Zhodou okolností sa aj oba členy triedy Point volajú xy, ale akýkoľvek samostatný výskyt identifikátorov xy v metóde move() tieto dva členy zakryje. Preto sme v tele metódy museli použiť zápis this.x = x. Jednoduché, nekvalifikované meno x napravo od operátora priradenia predstavuje formálny parameter metódy. Naľavo od operátora priradenia sa nachádza výraz this.x, ktorý sprístupňuje zakrytý člen x triedy Point. Kľúčové slovo this použité vnútri metódy predstavuje referenciu na objekt, nad ktorým bola metóda vyvolaná. Viac si povieme v odseku venovanom prístupu k členom triedy.

Metóda move() nevracia nijakú hodnotu. To je vyjadrené kľúčovým slovom void, použitým ako typ návratovej hodnoty v deklarácii. Na rozdiel od C++ je toto jediná situácia, v ktorej je možné kľúčové slovo void použiť.

Pozrime sa teraz na deklaráciu inej metódy, napr. getX(). Vidíme, že tá je deklarovaná s návratovým typom int. V tele metódy musíme preto zabezpečiť, aby sa nejaká hodnota typu int vrátila volajúcemu objektu. Ako vyplýva z názvu metódy, chceme zrejme vrátiť x-ovú súradnicu príslušného bodu, ktorá je uložená v člene x. Na určenie návratovej hodnoty použijeme príkaz return x. Tento príkaz v ľubovoľnej metóde spôsobí jej okamžité ukončenie a vrátenie príslušnej hodnoty. Je chybou použiť príkaz return bez argumentu v metóde, ktorá bola deklarovaná s návratovým typom iným ako void, rovnako je chybou použiť return s argumentom v metóde deklarovanej ako void.

Formálne parametre metódy môžu byť deklarované s modifikátorom final, v takom prípade sa v tele metódy považujú za konštantné a nemožno ich meniť.

Podobne ako pri deklarácii členov môžu byť súčasťou deklarácie metód triedy modifikátory. Pri metódach možno použiť modifikátory public, protected, private, abstract, static, final, synchronizednative. Prvé tri majú rovnakú funkciu ako pri členoch, teda definujú úroveň prístupu k metódam. Modifikátory abstractfinal súvisia s mechanizmom dedičnosti, takže si o nich povieme až nabudúce.

Popri statických členoch tried možno pomocou modifikátora static ako statické definovať aj metódy. Takéto metódy sa dajú zavolať aj v prípade, že nemáme k dispozícii nijaký objekt príslušnej triedy. V analógii s posielaním správ si možno predstaviť, že príslušnú správu neposielame konkrétnej inštancii triedy, ale samotnému metaobjektu triedy. Pre statické metódy sa vžilo aj pomenovanie class methods na rozdiel od bežných instance methods. Príklad statickej metódy možno opäť nájsť na webovej stránke PC REVUE.

Metódy označené ako synchronized slúžia na zabezpečenie vzájomného vylučovania pri prístupe k objektu (v podstate realizujú údajovú metaštruktúru monitor). Možnostiam synchronizácie v Jave bude venovaná samostatná časť.

Konečne metódy deklarované s modifikátorom native sú implementované pomocou platformovo závislého kódu. Takéto metódy neobsahujú telo, namiesto neho je deklarácia ukončená bodkočiarkou, ako napr.:

public native void fileOpen(String name);

Deklarácia konštruktorov

Špeciálnou množinou metód sú tzv. konštruktory. Deklaráciu konštruktora spoznáme ľahko: neobsahuje špecifikáciu návratového typu a jeho názov sa presne zhoduje s názvom triedy. Ako napovedá jeho názov, konštruktor je zodpovedný doslova za „skonštruovanie“ objektu pri jeho vytváraní. Konštruktorov môže byť viac, musia sa však líšiť počtom a/alebo typmi argumentov. Konštruktory okrem jednej výnimky nikdy nevoláme klasickým volaním metódy, vyvolávajú sa automaticky pri vzniku novej inštancie objektu.

Od bežných metód sa konštruktory líšia predovšetkým tým, že môžeme k ich deklarácii pripojiť len modifikátory prístupu public, protected či private. Menovite konštruktory nemôžu byť abstraktné ani finálne a už vôbec nie statické.

V triede Point sa nachádzajú dva konštruktory. Jeden akceptuje dva argumenty typu int, ktoré bežným spôsobom skopíruje do členov xy, druhý je bez argumentov. Všimnime si telo tohto konštruktora:

Point()
{
  this(0, 0);
}

Prvým (a v tomto prípade jediným) príkazom v tele konštruktora je this(0, 0), ktorý spôsobí explicitné zavolanie iného konštruktora, ktorý dokáže akceptovať dve celé čísla ako argumenty. Takýto konštruktor „zhodou okolností“ v triede Point máme, takže vytvorenie inštancie triedy Point bez argumentov vytvorí v skutočnosti bod s nulovými súradnicami. Tento prístup je pomerne bežný – najkomplikovanejší konštruktor sa implementuje celý, zjednodušené verzie konštruktora potom volajú hlavný konštruktor s určitými implicitnými hodnotami. Výhoda je zrejmá: pri zmene spôsobu konštrukcie objektu stačí prepísať kód jediného konštruktora.

V prípade, že trieda neobsahuje nijaký konštruktor, vytvorí prekladač tzv. implicitný konštruktor bez argumentov, ktorého jedinou úlohou bude zavolať konštruktor nadradenej triedy (ale o tom až nabudúce).

Práca s objektmi

Skôr než s objektmi začneme pracovať, musíme ich nejakým spôsobom vytvoriť. To sa deje v dvoch krokoch. Predovšetkým treba deklarovať premennú príslušného referenčného typu, v našom prípade teda napríklad:

Point p;

Podobne, ako to bolo pri poliach, ani teraz premenná p neobsahuje samotný objekt, dokonca dosiaľ žiadna inštancia triedy Point vytvorená nebola (p obsahuje hodnotu null).

Explicitné vytvorenie objektu typu Point a priradenie odkazu na tento objekt do premennej p zabezpečíme pomocou nasledujúceho príkazu:

p = new Point(10, –40);

Novú inštanciu teda vytvoríme pomocou kľúčového slova new, za ktoré uvedieme názov príslušnej triedy a do zátvoriek argumenty pre príslušný konštruktor. Vytvorenie inštancie sa vykoná v nasledujúcich krokoch:

1.    alokuje sa miesto v pamäti

2.    inicializujú sa členy objektu na implicitné (nulové) hodnoty

3.    vyhodnotia sa argumenty konštruktora

4.    vyberie sa a vykoná sa vhodný konštruktor

5.    referencia na nový objekt sa vráti ako hodnota celého výrazu

V prípade, že by sme chceli vytvoriť bod s implicitnými súradnicami, použijeme príkaz:

Point q = new Point();

Len čo je objekt vytvorený, možno s ním pracovať. Členy aj metódy sprístupňujeme pomocou klasickej bodkovej notácie:

objekt.člen

objekt.metóda

Keby niektorý z členov triedy Point bol verejne prístupný, mohli by sme napísať niečo ako:

p.x = 20;   // chyba
p.y = 50;   // chyba

ale vzhľadom na to, že oba členy sú privátne, uvedený zápis povedie k chybe pri preklade. Môžeme však objektu p poslať správu move alebo show (obe sú verejné):

p.move(20, 50);
p.show();

Použitie this

Na záver si ešte objasníme význam kľúčového slova this. V ľubovoľnej nestatickej metóde predstavuje this fiktívnu lokálnu premennú, ktorej obsahom je referencia na ten objekt, nad ktorým práve vykonávaná metóda pracuje. Ak teda napríklad objektu, na ktorý odkazuje prv definovaná premenná p, pošleme správu move, zavolá sa jeho metóda move()a v jej tele bude this obsahovať referenciu na tento objekt (inými slovami, bude platiť podmienka p == this).

Je nadovšetko zrejmé, že odkaz na volaný objekt nemá zmysel v statických metódach, takže v takýchto metódach povedie akékoľvek použitie kľúčového slova this k neodvratnej prekladovej chybe.

Nabudúce

V májovom čísle budeme v rozprávaní o triedach pokračovať – na rad príde dedičnosť, prekrývanie metód, abstraktné triedy a kadečo iné. Dovtedy dovidenia.

 

 

 


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

Mohlo by Vás zaujímať

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 ...

Ako na to 2

Tipy a triky: Ako vypnúť uzamykaciu obrazovku vo Windows 10?

29.11.2016 00:10

Rozčuľuje vás, že pred každým prihlásením doúčtu vášho počítača musíte prejsť uzamykacou obrazovkou? Windows 10 na tejto obrazovke ukazuje čas,dátum anejakú zaujímavú fotografiu zrôznych kútov sveta. ...

Žiadne komentáre

Vyhľadávanie

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

Najnovšie videá