Tentokrát si ukážeme, jak přidat ke třídě Animal z minulého dílu operace – metody. Nejprve si ukážeme, jak vypadá deklarace metod a vytvoříme několik instančních metod v duchu objektvé zásady zapouzdření. Poté si řekneme něco o životu objektů (konstruktory a garbage collection) a na závěr se naučíme vytvářet třídní metody a proměnné.
Deklarace instanční metody
Metoda je stejně jako třída tvořena hlavičkou a tělem. Hlavička začíná specifikátorem přístupu (public, private...) za ní následuje návratový typ, což je hodnota, kterou metoda musí vracet. Pokud metoda nemá vracet žádnou hodnotu, tak zde zapíšeme klíčové slovo void. Další položkou je název metody a v kulatých závorkách čárkou oddělené parametry volání.
Pojmenovávání metod a umístění závorek bloku
Názvy metod zapisujeme camelCasem a měly by být slovesem (vyjadřovat akci). Pro otevírací závorky bloků platí všeobecná konvence – jsou vždy na stejné řádce jako deklarace, ke které níž se vztahují. Zavírací závorky jsou vždy na samostatném řádku na úrovni prvního písmene deklarace.
Příklad
Metoda realizující pozdrav z minulého dílu by proto vypadala takto:
public class Animal { /** * Druh zviratka */ public String kind; /** * Zvuk, ktery vydava */ public String sound; /** * Metoda, ktera vypise pozdrav zviratka */ public void sayHello(){ System.out.println("Ja jsem " + kind + " a delam " + sound); } }
Protože se jedná o instanční metodu, která se vztahuje ke konkrétní instanci (a jejím datům), tak ji vždy musíme volat přímo na daném objektu, nikoliv na třídě (tak, jak jsme volali metody třídy Math).
Animal micina = new Animal(); micina.kind = "kocka"; micina.sound = "mnaaaauuu"; micina.sayHello(); //Animal.sayHello(); //tohle by byla chyba
Návratová hodnota
Pokud chceme, aby metoda vracela nějakou hodnotu, pak musíme jednak v hlavičce specifikovat její typ a zároveň musíme v těle metody uvést příkaz return, za kterým následuje proměnná, kterou chceme navrátit. V případě metod s návratovým typem void můžeme (ale nemusíme) pro ukončení vykonávání metody uvést příkaz return, za nímž již přímo následuje středník (konec příkazu). Po příkazu return je vždy metoda okamžitě ukončena, proto za ním již nesmí následovat žádný kód. Metoda ale smí obsahovat více těchto příkazů, pokud jsou v různých větvích programu, čehož budeme využívat později, až se naučíme vytvářet podmínky.
Předávání parametrů
Parametry volání jsou dvojice typ – název proměnné, které oddělujeme čárkou. Je velmi důžité vědět, že zatímco se primitivní datové typy předávají hodnotou (tj. dojde k jejich zkopírování), tak objektové typy se předávají odkazem (samotná reference se ale předává hodnotou). Z toho plyne, že pokud provedeme v metodě změnu hodnoty primitivní proměnné předané parametrem, tak se mimo funkci tato změna neprojeví. Pokud ovšem provedeme změnu na předaném objektu, tak se změna projeví (protože objekt samotný existuje pouze v jedné kopii), naopak přiřazení jiného objektu do reference by nemělo efekt (došlo by k přiřazení do kopie → originál mimo metodu by zůstal nedotčen).
Příklad
Jako příklad předáváni parametrů a návratu hodnot si zatím nemůžeme ukázat žádnou „chytrou“ metodu, protože nám k tomu chybí jazykové prostředky (cykly, podmínky). Proto si ukážeme jednu velmi důležitou zásadu objektově orientovaného programování – zapouzdření (encapsulation).
Tato zásada říká, že bychom se měli snažit ukrývat co možná nejvíce informací o implementaci, které uživatelé našich knihoven nepotřebují znát.
V praxi to pro naši třídu Animal znamená, že její členy (kind, sound) budou soukromé (private) a budeme k nim zvenčí přistupovat pomocí veřejných metod. U těchto proměnných je sice šance na změnu jejich implementace diskutabilní, ale uvažujme, že bychom nechali původní variantu (s přímým přístupem) a naše třída by časem evolvovala ve skutečně unikátní kousek technologie, který by při nastavení zvuku syntetizoval také zvuk reálný a uložil jej do nové proměnné.
V tento okamžik bychom se dostali do prekérní situace, kdy bychom již nemohli zakázat přístup do proměnné (přímý přístup již používají stovky zoologických zahrad, tisíce programů, miliony uživatelů) a zároveň přímý přístup neumožňuje žádnou notifikaci syntetizátoru, aby vytvořil nový zvuk při změně textu. Pokud bychom tedy měli například vránu, která by měla řetězec "krááá" a zvuk KRRÁÁÁÁ a změnili bychom napřímo řetězec na "haf" (vrána se naučila štěkat), tak nám ale ve zvuku bude instance vrány pořád krákat.
Kdybychom naproti tomu od počátku přistupovali pomocí metody, tak bychom do ní pouze přidali kód na vytvoření zvuku a vše by fungovalo jak má.
Buďme ale trochu při zemi. Bohatě by stačilo, abychom měli váhu zvířete v kilogramech a zároveň mnoho složité logiky, která by tuto informaci využívala a v tento okamžik přišel od zadavatele pokyn, že máme v rozhraní třídy poskytovat poskytovat váhu v imperiálních jednotkách (librách). S přímým přístupem neřešitelné (nutnost přepsat veškerou logiku na libry). S přístupem přes metody stačí přidat přepočet a vnitřek třídy může fungovat beze změny.
/** * Zviratko, ktere uz ma i nejake operace * @author Pavel Micka */ public class Animal { /** * Druh zviratka */ private String kind; /** * Zvuk, ktery vydava */ private String sound; /** * Metoda, ktera vypise pozdrav zviratka */ public void sayHello(){ System.out.println("Ja jsem " + getKind() + " a delam " + getSound()); } /** * @return the kind */ public String getKind() { return kind; } /** * @param kind the kind to set */ public void setKind(String kind) { this.kind = kind; } /** * @return the sound */ public String getSound() { return sound; } /** * @param sound the sound to set */ public void setSound(String sound) { this.sound = sound; } }
Gettery a settery
Jak jste si asi všimli, tak názvy metod, které ukládají novou hodnotu proměnné začínají na set a názvy metod, které získávají hodnotu proměnné na get a v obou případech pak následuje jméno proměnné (s velkým počátečním písmenem). Tzv. gettery a settery jsou velmi důležitou konvencí, kterou využívají mnohé frameworky (ty si můžete zatím představit jako velké knihovny). Jedinou výjimkou z této konvence jsou proměnné typu boolean, název jejichž getteru začíná na is (isFriendly()).
Dobrou zprávou při použití vývojového prostředí je, že tyto metody nemusíme psát, budou nám na vyžádání vygenerovány. V Netbeans klikneme pravým tlačítkem do kódu a z menu vybereme Refactor/Encapsulate Fields. Pomocí wizzardu pak naklikáme, které metody chceme nechat vytvořit.
Na tomto místě si ještě povíme o klíčovém slovu this (jenž je součásti generovaných setterů), které slouží jako ukazatel na aktuální instanci. Tímto slovem dochází k rozlišení, zda-li používáme proměnnou parametru metody nebo proměnnou dané instance. Kdybychom this nepoužili, tak by v případě setteru u sound došlo k uložení řetězce opět do proměnné parametru (tj. nemělo by to na instanci žádný vliv).
Konstruktor
Konstruktor je speciální funkcí, která se jmenuje stejně jako třída, ve které se nachází. Při jeho deklaraci nepoužíváme návratový typ ani klíčové slovo void.
Konstruktor slouží k inicializaci objektu a jeho obsahu a je metodou, která se zavolá automaticky při vytváření každého objektu. Dokonce i naše třída Animal obsahuje konstruktor, ač jsme jej nespecifikovali – Java do každé třídy, která neobsahuje žádný konstruktor, přidává implicitní konstruktor, který nemá žádné parametry.
Destrukce objektu
Opakem konstrukce objektu je jeho destrukce. V některých jazycích si musíme o zničení objektu a uvolnění paměti, kterou zabírá, explicitně zažádat. V Javě existuje garbage collection, což je automatická fáze v běhu programu, během které se zničí všechny objekty, na něž nemůžeme syntakticky nijak dosáhnout.
O garbage collection můžeme sice také zažádat pomocí volání System.gc(), ale není zaručeno, že nám virtuální stroj vyhoví a skutečně kolekci zavolá, případně že zničí i náš objekt (garbage collection je velmi složitý proces). Z tohoto důvodu se volání kolekce vesměs necháváme na virtuálním stroji, aby se sám rozhodl, kdy je nejvhodnější okamžik.
finalize()
Podobně jako existuje konstruktor, tak v Javě existuje metoda finalize() (dokumentace), kterou mají všechny objekty, a jež je volána jako poslední pomazání před zničením objektu.
Tuto metodu můžeme ve svých objektech překrýt (znovu deklarovat a implementovat), ale její použití je vhodné pouze pro velmi specifický kód, jelikož nevíme, kdy přesně dojde k jejímu zavolání.
Třídní metody a proměnné
Než přejdeme k příkladu, tak si ještě řekneme o klíčovém slovu static, které píšeme za specifikátor přístupu. Toto slovo používáme, pokud chceme specifikovat, že se daná metoda nebo proměnná netýká žádné instance, ale třídy jako celku. Statické metody jsme používali u již zmíněné třídy Math (vzpomeňte si, že jsme nevytvářeli žádnou instanci).
Příklad
Naše zvířátko obohatíme o konstruktory, ukážeme si, že pro minimalizaci duplicity kódu můžeme volat z jednoho konstruktoru jiný konstruktor. Také přidáme statickou proměnnou, pomocí které budeme počítat všechna vytvořená zvířátka. Do této proměnné budeme přistupovat pomocí getteru, setter zavádět nebudeme, protože je nežádoucí, aby nám tuto hodnotu někdo přepisoval.
package beings; /** * Zviratko, ktere uz ma i nejake operace * @author Pavel Micka */ public class Animal { /** * Tridni promenna, ktera znaci pocet vytvorenych zviratek */ private static int createdCount = 0; /** * Druh zviratka */ private String kind; /** * Zvuk, ktery vydava */ private String sound; /** * Bezparametricky konstruktor, pouze prida 1 k poctu zviratek */ public Animal() { createdCount++; } /** * Konstruktor, ktery prijima jako parametry druh zvirete a zvuk, ktery zvire vydava * @param kind * @param sound */ public Animal(String kind, String sound) { this(); //volani bezparametrickeho konstruktoru, zretezene volani konstruktoru musi byt vzdy jako prvni this.kind = kind; this.sound = sound; } /** * Vrati pocet vytvorenych zviratek * @return pocet */ public static int getCreatedCount() { return createdCount; } /** * Metoda, ktera vypise pozdrav zviratka */ public void sayHello(){ System.out.println("Ja jsem " + getKind() + " a delam " + getSound()); } /** * @return the kind */ public String getKind() { return kind; } /** * @param kind the kind to set */ public void setKind(String kind) { this.kind = kind; } /** * @return the sound */ public String getSound() { return sound; } /** * @param sound the sound to set */ public void setSound(String sound) { this.sound = sound; } }
package objects; //jsme v balicku objects import beings.Animal; //abychom mohli zviratko pouzit primo, tak jej importujeme /** * Hlavni trida obsahujici metodu main * @author Pavel Micka */ public class Main { /** * Vstupni mod aplikace * @param args the command line arguments */ public static void main(String[] args) throws InterruptedException { Animal alik = new Animal(); //konsruktor bez paramanetru alik.setKind("pes"); alik.setSound("haf haf"); System.out.println("Pocet zviratek: " + Animal.getCreatedCount()); //1 //Nyni vytvorime dalsi zviratko - kocku Animal micina = new Animal("kocka", "mnaaaauuu"); //konstruktor s parametry micina.sayHello(); //micino pozdrav! :-] System.out.println("Pocet zviratek: " + Animal.getCreatedCount()); //2 } }