4. ZÁKLADNÍ POJMY Z OBJEKTOVĚ ORIENTOVANÉHO PROGRAMOVÁNÍ OBJEKT Program v Javě je staticky strukturován na třídy, jejichž instance (objekty) za běhu dynamicky programu vznikají a zanikají. Objekt je nejprve vytvořen (instanciován), následně může být používán a nakonec je zrušen. VYTVOŘENÍ OBJEKTU K vytvoření objektu slouží operátor new, jehož základní syntaxe: new voláníKonstruktoru Operátor alokuje paměť pro objekt a zavolá konstruktor, což je speciální metoda, která provádí inicializaci objektu. Jméno konstruktoru je vždy shodné se jménem třídy objektu. Příklad: new String("ahoj"); vytvoří objekt třídy String a inicializuje ho konstruktorem s parametrem "ahoj". Operátor new vrací referenci, odkazující na vytvořený objekt. Tato reference se při vytvoření objektu obvykle zároveň ukládá do referenční proměnné. Příklad: String retezec;
//
deklarace proměnné
retezec = new String("ahoj");
//
přiřazení reference
nebo zkráceně: String retezec = new String("ahoj"); POUŽÍVÁNÍ OBJEKTU Používání objektu spočívá ve volání metod a přímé manipulaci se členskými proměnnými. Pro přístup k proměnné nebo metodě se používá tečková notace: reference.jménoProměnné reference.voláníMetody Příklad String retezec = new String("ahoj");
// vytvoření objektu
retezec = retezec.concat("!");
// volání metody
ZRUŠENÍ OBJEKTU Java neumožňuje přímé rušení (dealokaci) objektu. Objekt je zrušen automaticky až tehdy, neexistuje-li na něj reference. Jsou například rušeny všechny objekty lokálních proměnných při ukončení metody. "Ruční" zneplatnění reference se provede přiřazením hodnoty null příslušné referenční proměnné.
TŘÍDA Pro každý objekt musí být před jeho vytvořením deklarována třída, ať už navržená v programu nebo knihovní. Knihovní třídy jsou uloženy v balících. D EKLARACE TŘÍDY Nejjednodušší deklarace třídy má syntaxi: class JménoTřídy { // tělo třídy } Identifikátor JménoTřídy slouží jako objektový typ, například v deklaraci referenční proměnné. V těle třídy mezi složenými závorkami mohou být deklarace členských proměnných a/nebo deklarace a definice metod - obvykle v tomto pořadí, ale není to vyžadováno. Všimněte si, že za uzavírající závorkou deklarace třídy není středník. Obecná deklarace třídy vypadá takto: [ modifikátory ] class jménoTřídy [ extends jménoRodičovskéTřídy ] [ implements seznamRozhraní ] { // tělo třídy } modifikátory mohou být následující:
public - označuje veřejnou třídu. Veřejná třída je přístupná i mimo balík, kde je deklarována. Není-li třída veřejná, je přístupná pouze ve svém balíku. abstract - třída deklarovaná s tímto modifikátorem je abstraktní a nesmí být nikdy instanciována.
Nejsou-li uvedeny příslušné modifikátory, je třída implicitně pokládána za neveřejnou, neabstraktní a ne-koncovou. Zároveň nelze použít modifikátory abstract a final.
Za jménem třídy může být specifikována rodičovská třída pomocí klíčového slova extends(angl. rozšiřuje). Třída dědí proměnné a metody třídy rodičovské. Za klíčovým slovem implements následuje jedno nebo více jmen rozhraní, které třída implementuje. To mimo jiné znamená, že třída musí obsahovat definice metod uvedené v příslušném rozhraní. Je-li jmen rozhraní více, jsou oddělena čárkami.
METODY Nejjednodušší definice metody má tvar: NávratovýTyp jménoMetody ( parametry ) { // tělo metody } NávratovýTyp musí být datový typ. Návrat z metody a případné předání návratové hodnoty způsobí příkaz return. Je-li návratovým typem void, metoda nevrací hodnotu. jménoMetody musí být platný identifikátor. Pokud mají v jedné třídě dvě metody shodná jména, musí mít odlišný počet a/nebo typ parametrů - tzv. přetěžování metod (overloading). Při volání přetížené metody překladač na základě typů argumentů rozhodne, kterou volat. Díky přetěžování nemusí existovat různě pojmenované metody, jejichž funkce je prakticky shodná. Příklad: Argumentem metody System.out.println(), která provádí výpis na standardní výstup, může být proměnná kteréhokoliv základního datového typu nebo nic. System.out.println();
// odřádkuje
System.out.println("ahoj");
// vypíše ahoj (1)
System.out.println(12345);
// vypíše 12345 (2)
Ve všech případech se jedná o volání jiné přetížené metody - překladač se rozhoduje na základě argumentů - první případ se liší počtem argumentů, druhé dva jejich typem: (1) předává řetězec, (2) celé číslo. Na pořadí definic metod nezáleží - dopředné reference překladač zpracovává automaticky. Formální parametry metody:
Nejsou-li parametry uvedeny, jde o metodu bez parametrů. Každý parametr má tvar: [final] typ jménoParametru
Modifikátor final, který lze použít až od JDK1.1, značí konstantní hodnotu (nelze měnit hodnotu argumentu v metodě). V těle metody nelze deklarovat lokální proměnnou pojmenovanou stejně jako parametr. Je-li parametrů více, jsou odděleny čárkami
Je-li jméno parametru stejné jako jméno členské proměnné, dochází k jejímu zastínění (hiding) parametrem. K explicitnímu přístupu ke členské proměnné pak slouží operátor this, který vrací referenci objektu na sebe Příklad: class Trida { int x; Trida (int x) { // zastínění členské proměnné x parametrem this.x = x; // přiřazení hodnoty parametru členské proměnné x // ... } }
Obecná deklarace metody vypadá takto: [ práva ] [static] [abstract] [final] [native] [synchronized] NávratovýTyp jménoMetody ( parametry ) [throws seznamVýjimek ]
práva - tato položka určuje přístupnost metody vně třídy a pravidla pro dědičnost. Metoda může být buď veřejná (public), chráněná (protected), soukromá (private) nebo nemusí mít práva specifikována vůbec. static - označuje statickou metodu. Statické metody je možné narozdíl od nestatických volat i tehdy, neexistuje-li žádná instance dané třídy. Mohou však přímo manipulovat pouze se statickými (a svými lokálními) proměnnými. Volání statické metody má tvar: [ JménoTřídy. ] jménoMetody ( parametry ); JménoTřídy se nemusí specifikovat uvnitř třídy, která statickou metodu definuje. V jedné třídě nemůže být definována shodně pojmenovaná statická i nestatická metoda se stejným počtem a typem parametrů - jinak dojde k chybě při překladu. abstract - metody deklarované jako abstraktní nemají v dané třídě tělo a musí být definovány až potomky třídy. Třída, která obsahuje abstraktní metody, nemůže být instanciována a musí být rovněž deklarována jako abstraktní. synchronized final - označuje koncovou metodu. Koncová metoda nesmí být potomky překryta. native - nativní metody umožňují sloučit programy psané v Javě a jiných jazycích. Například většina nízkoúrovňových metod z Java Core API je nativních. V běžných
aplikacích se nativní metody nepoužívají kvůli jejich nepřenositelosti. V appletech se používat nesmí. Klíčové slovo throws uvozuje jedno nebo více jmen tříd výjimek, které může metoda vyvolat. Je-li výjimek více jsou odděleny čárkami.
KONSTRUKTORY Konstruktor je speciální metoda, která se volá pouze při vytváření objektu a slouží k jeho inicializaci. Jméno konstruktoru musí být shodné se jménem třídy, v níž je definován. Návratový typ konstruktoru se neuvádí. Pro konstruktory platí pravidla uvedená pro metody. Jejich deklarace však nesmí obsahovat modifikátory abstract, final, native, static a synchronized a nedochází k dědění konstruktorů. Příklad:
class Bod { Bod() { // konstrukor třídy Bod // ... } } Každá třída má alespoň jeden konstruktor. Pokud ve třídě konstruktor není uveden, vytvoří překladač automaticky implicitní konstruktor bez parametrů (default constructor), který nic neprovádí. Konstruktor každé třídy musí na začátku obsahovat volání konstruktoru rodičovské třídy, aby se zajistila řádná inicializace zděděných proměnných (tzv. řetězení konstruktorů). K volání rodičovského konstruktoru slouží klíčové slovo super. super( parametryKonstruktoru ); Toto volání musí být provedeno jako první, aby se zajistila řádná inicializace v rodičovské třídě. Volání rodičovského konstruktoru lze vynechat pouze v případě, že rodičovská třída má pouze jeden konstruktor bez parametrů nebo konstruktor implicitní - pak toto volání překladač na začátek konstruktoru sám doplní. Volání konstruktoru téže třídy se provádí pomocí operátoru this. Volat konstruktor však lze opět pouze z konstruktoru a toto volání musí být provedeno jako první. ČLENSKÉ PROMĚNNÉ Členské proměnné se deklarují v těle třídy - mimo těla metod (jinak by se jednalo o lokální proměnné). Podle konvence se deklarace členských proměnných uvádějí před deklaracemi metod.
Pro členské proměnné platí podobná pravidla jako pro lokální proměnné. Jejich deklarace může navíc obsahovat následující modifikátory: [ práva ] [static] [transient] [final] [volatile] typ jménoProměnné Pokud není členská proměnná v deklaraci inicializována, je jí přiřazena implicitní hodnota:
nula - pro celočíselné a racionální typy, \u0000 - pro typ char, false - pro logický typ, null - pro referenční typy (neplatná reference).
práva - tato položka určuje přístupnost proměnné vně třídy a pravidla pro dědičnost. Proměnná může být buď veřejná (public), chráněná (protected) nebo soukromá (private) nebo nemusí mít práva specifikována vůbec. static - statická proměnná existuje jen v jednom exempláři, který všechny objekty dané třídy (včetně potomků) sdílí, tj. při vytváření instance třídy není pro statickou proměnnou znovu alokována paměť. Statické proměnné jsou přístupné (v souladu s přístupovými právy) i tehdy, neexistuje-li (ještě) instance třídy. Ke statické proměnné se přistupuje touto konstrukcí: JménoTřídy.jménoProměnné transient - informuje, že proměnná není součástí perzistentního stavu objektu - systém nebude její hodnotu uchovávat při zasílání objektu po síti apod. volatile - zaručí, že obsah proměnné bude aktualizován při každém jejím použití (čtení, přiřazení) - používá se tehdy, přistupuje-li se v programu k proměnné asynchronně a při každém čtení proměnné je třeba získat aktuální hodnotu. Java zaručuje, že hodnota libovolné proměnné je konzistentní s výjimkou typů long a double, které by měly být vždy deklarovány s modifikátorem volatile, je-li k nim prováděn asynchronní přístup. final - označuje konstantu. Její obsah je nutno inicializovat při deklaraci a nesmí se v programu měnit (překladač ohlásí chybu).
DĚDIČNOST Třída-potomek se od rodičovské třídy odvodí v deklaraci pomocí klíčového slova extends. Potomek od přímého rodiče dědí, tj. přejímá jako by byly znovu definovány, všechny členské proměnné a metody, které:
jsou deklarovány jako veřejné (public) nebo chráněné (protected), nemají explicitně určena přístupová práva a zároveň se rodičovská třída nachází ve stejném balíku jako potomek.
Potomek nedědí členské proměnné a metody, které:
jsou deklarovány jako soukromé (private) , jsou deklarovány v jiném balíku a nejsou veřejné (public), mají v potomkovi stejné jméno (a typy parametrů - u metod) jako v rodičovské třídě. Tyto členské proměnné (metody) jsou předefinovány - hovoří se o zastínění proměnných a překrytí metod.
Při překrývání metod je možné přístupová práva pouze rozšířit (na public nebo protected - v případě implicitních práv). Naopak seznam (resp. typy) výjimek deklarovaných pomocí throws lze pouze zúžit. Na změny modifikátorů a členské proměnné omezení kladena nejsou. K překrytým metodám a zastíněným proměnným rodiče se přistupuje pomocí operátoru super, který vrací referenci na rodičovskou třídu: super. jménoČlenskéProměnné super. jménoMetody Příklad: class A { void a() throws Exception { // ... } } class B extends A { void a() { // zúžení seznamu výjimek try { super.a(); // volání metody rodiče: A.a a(); // volání sama sebe // ... } catch (Exception e) {} } }
PŘÍSTUPOVÁ PRÁVA Členské proměnné a metody mají specifikována přístupová práva určující okruh tříd, které k nim mají přístup. Přístupem se rozumí možnost přímé manipulace se členskými proměnným a volání metod. Třída má přístup pouze k metodám a členským proměnným, které:
sama deklaruje, dědí,
jsou deklarovány jako public v jiných veřejných třídách, jsou umístěny ve třídě téhož balíku a zároveň mají přístup nespecifikován nebo nastaven jako public či protected.
Třída nemá přístup ke členským proměnným a metodám z jiných balíků, které jsou soukromé, chráněné nebo mají nespecifikován přístup a dále ke třídám z jiných balíků, které nejsou deklarovány jako veřejné.
TŘÍDA OBJECT Třída Object z balíku java.lang je kořenovou třídou ve stromu tříd, tj. všechny třídy, ať už knihovní nebo navržené programátorem, mají společného (nepřímého) rodiče třídu Object. Object definuje základní metody, které musí mít každý objekt v Javě - většinu z nich používá runtime systém. Jedná se o metody:
protected native clone() - vytvoří identický objekt (ale nevolá konstruktor) a přiřadí stejné hodnoty všem členským proměnným. Funguje pouze u tříd, které implementují rozhraní Cloneable. public boolean equals(Object obj) porovnává objekt s objektem obj (způsob porovnávání se pro jedntlivé potomky liší). public final Class getClass() - vrací objekt reprezentující třídu instance v runtime systému. protected void finalize() throws Throwable - je volána při úklidu; standardně neprovádí nic. public int hashCode() - používá se k hašování , není-li překrytá vrací totéž co metoda System.identityHashCode(). public String toString() - vrací identifikační řetězec objektu (často předefinovávaná metoda pro účely ladění). wait(), notify(), notifyAll().
ROZHRANÍ (INTERFACE) Rozhraní (interface) je syntaktická struktura obsahující deklarace konstant a metod (bez implementací). Deklarace rozhraní je podobná deklaraci třídy: [public] interface jménoRozhraní [ extends seznamRozhraní ] { // tělo rozhraní }
public - označuje veřejné rozhraní. Veřejné rozhraní je přístupné i mimo balík, kde je deklarováno. Není-li rozhraní veřejné, je přístupné pouze ve svém balíku. V nepovinné části za klíčovým slovem extends (angl. rozšiřuje) následuje jedno nebo více jmen rozhraní, jejichž konstanty a metody toto rozhraní dědí .
Děděné deklarace konstant a metod z více rodičovských rozhraní nesmí kolidovat, podobně jako je tomu u deklarací v rámci jedné třídy . Tělo rozhraní smí obsahovat pouze deklarace konstant a metod. Při tom platí následující pravidla:
Všechny metody a konstanty uvedené v deklaraci rozhraní jsou automaticky veřejné (public) a nesmí mít specifikována přístupová práva protected ani private. Všechny metody jsou navíc automaticky abstraktní. I když u proměnné není modifikátor final, jedná se vždy o konstantu.
V rozhraní nesmí být použity modifikátory: transient, volatile a synchronized. Rozhraní se používají k zachycení společných prvků tříd, které spolu nemusí souviset ve stromu dědičnosti - společnými prvky jsou konstanty a metody deklarované rozhraním. Třída, která implementuje dané rozhraní, musí obsahovat definice všech(!) metod tohoto rozhraní. Rozhraní pak lze použít jako objektový typ třídy. Příklad: Program bude obsahovat schránku (clipboard), přes kterou lze kopírovat, a vkládat objekty (text, obrázky, zvuky). Všechny objekty, které lze do schránky umístit, musí kopírování samy umožňovat - musí obsahovat metodu copy(). Z hlediska návrhu není vhodné, aby třídy takto rozdílných objektů měly společného rodiče třídu, deklarující metodu copy(), kterou všechny povinně zdědí. Výhodnější je deklarovat rozhraní (Clip) obsahující metodu copy(), které budou jednotlivé třídy implementovat - i v tomto případě bude zaručena podpora kopírování ze strany objektů. public interface Clip { byte[] copy(); } Metoda copy() bude vracet pole bytů reprezentující objekt ve schránce. Každá třída, jejíž instance lze kopírovat přes schránku, musí implementovat rozhraní Clip. Její deklarace bude: class Obrazek implements Clip {
byte[] copy() { // ... } } Schránka pak může obsahovat libovolný objekt typu Clip: class Schranka { byte[] schranka;
// metoda pro vložení (kopírovatelného) objektu public void vlozit(Clip objekt) { rozhraní schranka = objekt.copy(); schránky
// typ parametru je // vložení objektu do
} // ... }
INICIALIZACE TŘÍD A ROZHRANÍ K inicializaci třídy nebo rozhraní dochází při prvním aktivním použití, které nastane, je-li splněna aspoň jedna z podmínek :
je vyvolána metoda nebo konstruktor deklarovaný danou třídou. je vytvořeno pole s prvky typu dané třídy, je proveden přístup k nekonstantní členské proměnné třídy nebo konstantě rozhraní, je proveden přístup ke členské proměnné třídy s modifikátorem final nebo static, která je inicializována hodnotou vypočítanou za běhu programu.
Inicializace třídy se skládá z inicializace statických členských proměnných a vykonání statických inicializátorů (viz dále), přičemž před tím musí být inicializována nejprve její rodičovská třída. Inicializace rozhraní spočívá pouze v inicializaci v něm definovaných konstant, nedochází automaticky k inicializaci rodičovského rozhraní.