Základy objektů
Kromě uvedených základních typů můžeme ještě používat proměnné, ve kterých je uložen odkaz na objekt. Každý objekt má také svůj typ - třídu. Slovo třída tedy používáme k označení typu objektu. Máme-li vytvořenu (deklarovánu) třídu, můžeme vytvářet jednotlivé objekty (instance) této třídy. Každý objekt má nějaké vlastnosti a metody (funkce), které s těmito vlastnostmi pracují. Co je vlastností nebo metodou kterého objektu je právě určeno v deklaraci třídy, ze které bude tento objekt vytvořen. Chceme-li například vytvářet objekty, ve kterých budou uloženy některé údaje o zákaznících, vytvoříme třídu Zákazník následující deklarací (viz. Obrázek Třídy a objekty):
class Zakaznik { int cislo; float dluh; void zaplatil(float kolik) { dluh = dluh - kolik; int vratDeset() { return 10; } } } |
Tento zápis deklaruje novou třídu zákazníků - každý zákazník bude mít své číslo a dále mu bude přiřazen nějaký dluh. Pokud zákazník zaplatí, můžeme zavolat metodu zaplatil s parametrem kolik, která od jeho stávajícího dluhu odečte zadanou částku.
Stejně jako u každé vlastnosti (proměnné) je i u každé metody před jejím jménem typ. Tento typ určuje typ návratové hodnoty, kterou tato metoda vrací jako výsledek své činnosti. Ukončení metody a návrat nějaké hodnoty zajišťuje použití speciálního příkazu return následovaného hodnotou, která má být vrácena:
int vratDeset() { return 10; } |
Speciální klíčové slovo void říká, že daná metoda nevrací žádnou návratovou hodnotu.
Pokud máme výše uvedou třídu zákazník, můžeme ji použít následujícím způsobem:
Zakaznik novak = new Zakaznik(); novak.cislo = 124; novak.dluh = 10000; novak.zaplatil(4500); //sníží dluh o 4500 int i=novak.vratDeset(); // Do proměnné i uložíme výsledek metody vratDeset() |
První řádek vytvoří nový objekt typu Zakaznik a uloží odkaz na tento objekt do proměnné novak. S odkazem na vytvořený objekt je dále možné pracovat tak, že používáme vlastnosti daného objektu, nebo voláme jeho metody. K vlastnostem nebo metodám vytvořeného objektu máme přístup pomocí tečkové notace se jménem proměnné, do které byl uložen odkaz na náš objekt. Nové objekty jsou tedy vytvářeny pomocí volání new, o jejich odstraňování se nemusíme starat. Je starostí systému, aby zjistil, odkdy už daný objekt není potřeba. V okamžiku, kdy je zjištěno, že objekt už není potřebný, je objekt automaticky odstraněn. To velice usnadňuje tvorbu zvláště větších programů - explicitní dealokace paměti je mnohdy zdrojem nepříjemných chyb - jak je tomu např. při vývoji v C nebo C++.
class Trida { int velikost; public Trida() { ... } public Trida(int jaka) { velikost = jaka; } .. } |
První konstruktor nemá žádné parametry, bude tedy vyvolán, pokud při vytváření nezadáme žádný parametr. Pokud při volání new zadáme jeden parametr typu int, bude zavolán druhý konstruktor. Při vytváření objektu rozhodneme, který konstruktor se zavolá, následujícím způsobem:
... NovaTrida x = new NovaTrida(10); // zavolá konstruktor s parametrem int |
Kromě vyvolávání mají konstruktory ještě několik zvláštních vlastností. Důležitou vlastností odlišující konstruktory od normálních metod je to, že se nedědí. Místo toho však je na začátku konstruktoru zavolán zděděný konstruktor. Pokud chceme zavolat zděděný konstruktor s parametry, můžeme jako první příkaz konstruktoru uvést speciální příkaz super s příslušnými parametry:
class NovaTrida extends Trida{ public NovaTrida() { super(10); } |
Příkaz super volá zděděný konstruktor s uvedeným parametrem. Druhou možností je další speciální klíčové slovo this:
public NovaTrida(float x) { this(); // zavolá konstruktor bez parametrů ...// provedení vlastních inicializací } |
Příkaz this volá jiný konstruktor ze stejné třídy (s příslušnými parametry). Pokud jako první příkaz konstruktoru neuvedeme ani jedno z těchto klíčových slov, konstruktor se chová, jakoby jeho první příkaz byl super(). Když domyslíme tento systém volání konstruktorů, dojdeme k závěru, že při konstrukci každého objektu je nejprve zavolán konstruktor ve třídě Object, pak následují všechny konstruktory na příslušné větvi v hierarchii dědění a nakonec se zavolá konstruktor napsaný ve vytvářené třídě. Pokud u své třídy neuvedeme žádný konstruktor, překladač vytvoří implicitní prázdný konstruktor bez parametrů. To, že je prázdný, se projeví zavoláním implicitního super() jako prvního a posledního příkazu, tj. provede se zděděný konstruktor.
Klíčová slova this a super mohou být použita kromě konstruktorů také pro přístup k proměnným a metodám daného objektu. Při tomto druhém použití se však s nimi váže jiná syntaxe. Klíčové slovo this použijeme v případě, že chceme přistoupit k proměnné nebo metodě aktuálního objektu. Protože však k aktuálnímu objektu se přistupuje implicitně, tj. vždy pokud nestanovíme jinak, má použití klíčového slova this smysl asi pouze v případě, překryjeme-li si nějakou proměnnou lokální proměnnou:
class T { int x; void nastavX(int x) { this.x = x; } } |
Proměnná x příslušející instanci objektu byla v uvedené metodě zastíněna parametrem x - hodilo se nám tedy použít klíčové slovo this pro přístup k proměnné objektu. Klíčové slovo super se, podobně jako u konstruktorů, odkazuje na zděděnou metodu nebo vlastnost. Jeho nejčastější použití je u metod. Pokud již nějaká metoda obsahuje kód a my chceme v potomkovi její chování rozšířit, můžeme zavolat zděděný kód pomocí super s následující syntaxí:
class U extends T { void nastavX(int x) { super.nastavX(x); // proveď další akce } } |
Při použití this a super je třeba si vždy uvědomit, zda jsme v konstruktoru nebo v obyčejné metodě - v každém z uvedených případů se totiž používají trochu jiným způsobem. Pokud se zamyslíme nad účelem volání zděděných metod, zjistíme, že při volání super.metoda() musí být rozhodnuto o tom, která metoda se zavolá, již při překladu. Použijeme-li terminologii C++, klíčové slovo super v tomto případě vypíná mechanismus volání virtuálních metod, který je jinak přítomen všude při normálním volání metod v Javě.