COMPUTERWORLD
Specializovaný týdeník o výpočetní technice
o Internetu
(CW 44/97)

Programování v jazyce Java (3. díl)

David Štrupl

Dědění

Pouhé vytváření tříd a objektů by nepřinášelo prakticky nic nového oproti standardnímu (procedurálnímu) způsobu programování. Důležitým a často používaným rysem skutečně objektových jazyků je použití tzv. dědičnosti. Dědičnost je možnost vytvářet tzv. potomky již existujících tříd. Každý potomek je nová třída, která má všechny vlastnosti svého předka a může k nim přidat ještě některé další.

Např. máme-li definován typ Zakaznik, můžeme chtít vytvořit nový druh zákazníka, který bude mít vlastnosti původního typu -- ale kromě toho ještě umožní přidat proměnnou, která určuje slevu, přiřazenou dobrému zákazníkovi:

class DobryZakaznik extends Zakaznik {
int sleva;
}

Klíčové slovo extends v definici třídy znamená rozšíření původního typu -- v našem příkladu vytvoření nového potomka třídy Zakaznik. Potomek přitom dědí všechny vlastnosti původní třídy. Přidávání vlastností je pouze jeden aspekt vytváření rozšíření již existujících tříd. Druhou, možná důležitější možností je tzv. přepsat některé metody vyskytující se ve třídě předka. Např. chceme-li změnit způsob placení tak, aby odpovídal udělené slevě, můžeme napsat novou definici třídy DobryZakaznik:

class DobryZakaznik extends Zakaznik {
int sleva;
void zaplatil(float kolik) {
dluh = dluh -- (kolik+sleva);
}
}

Nová definice metody zaplatil zakrývá původní definici uvedenou ve třídě Zakaznik. Aby došlo k zakrytí staré metody metodou novou, musí mít obě stejnou deklaraci, tj. musí se stejně jmenovat, mít stejné parametry a stejný typ návratové hodnoty. K čemu by mohlo být takové zakrytí dobré? Představte si, že máte na jiném místě programu metodu pracující nějakým komplexním způsobem se zákazníky:

void pracujSeZakaznikem(Zakaznik x) {
...
x.zaplatil(1000);
}

Tuto metodu můžeme zavolat s parametrem typu Zakaznik nebo s libovolným potomkem třídy Zakaznik -- např. jako parametr může být uveden nějaký dobrý zákazník. V proměnné x, která je typu Zakaznik, se může vyskytovat odkaz na libovolného potomka uvedené třídy. Této vlastnosti použití odkazů na objekty se někdy říká polymorfismus neboli mnohotvarost. V době, kdy píšeme metodu pracujSeZakaznikem, totiž nevíme, jakého přesně druhu bude objekt dosazený jako parametr do proměnné x (Zakaznik nebo DobryZakaznik).

V jazyce Java existuje jedna třída, která má tak trochu výsadní postavení. Tato třída se jmenuje Object (přesněji java.lang.Object -- je totiž uložena v balíku java.lang). Pokud při definování nové třídy neuvedeme rodičovskou třídu, je implicitně dosazena třída Object. To má za následek, že všechny třídy jsou buď přímými, nebo nepřímými potomky třídy Object (viz obrázek Dědění od třídy Object). To má některé výhodné vlastnosti -- např. všechny metody definované pro třídu Object mají úplně všechny objekty vyskytující se v Java systému.

Dále, do proměnné deklarované jako proměnná této třídy můžeme uložit odkaz na libovolný objekt -- vyplývá to z možnosti ukládat do proměnných odkazy na instance zadaného typu nebo libovolného potomka. Příklad správného uložení odkazu na objekt:

Object z = new Zakaznik();

Tento příkaz je v pořádku, protože třída Zakaznik je potomkem třídy objekt (jako ostatně i všechny ostatní třídy).

Program v jazyce Java

Program v jazyce Java se skládá z úvodní sekce a z několika definic tříd. Jako první příkaz můžeme použít příkaz package, který určuje, součástí kterého balíku je naše třída:

package mujBalik;

Připomeňme si, že balík v podstatě znamená adresář, ve kterém bude umístěn přeložený class soubor.

Za příkazem package může následovat několik příkazů import. Příkaz import nám zkracuje zápis tříd. Součástí úplného jména každé třídy je totiž jméno balíku, ze kterého pochází. Plný zápis jména třídy (v typu proměnné) by mohl být poněkud dlouhý. Např. pokud pracujeme na projektu uloženém v balíku velkyProjekt a ještě každý pracovník má balik na své třídy (např. novak) museli bychom proměnné třídy zákazník z tohoto balíku deklarovat následujícím způsobem:

velkyProjekt.novak.Zakaznik x;

Pokud se chceme opakování tohoto zápisu vyhnout, můžeme použít příkaz import buď na třídu zákazník, nebo na celý balík novak:

import velkyProjekt.novak.Zakaznik;
import velkyProjekt.novak.*;

Pokud je na začátku našeho souboru jeden z těchto příkazů, můžeme deklarovat proměnné typu Zakaznik stejně, jakoby byly uloženy v našem aktuálním balíku.

Celý zdrojový text v jazyce Java bude tedy obsahovat následující části:

package jmeno;
import java.util.*;
public class MojeTrida extends Rodic {
// proměnné (vlastnosti)
// metody
}

Každý program by měl obsahovat jednu třídu, která je hlavní -- v případě samostatné aplikace je to třída obsahující main, v případě appletu je to potomek třídy Applet. Tato hlavní třída bývá označena klíčovým slovem public, které určuje, že třída je přístupná i z jiných balíků, než ve kterém se právě nacházíme.

POZOR: třída označená jako public může být v souboru jenom jedna a musí (!!!) se jmenovat stejně jako jméno souboru, ve kterém je uvedena (jméno souboru je tedy jméno této třídy zakončené koncovkou .java). Soubor může obsahovat ještě deklarace pomocných tříd -- tyto třídy však nemohou být označeny jako public, tj. nemohou být použity z jiných balíků (a souborů). Pokud chceme vytvořit třídu použitelnou i mimo náš zdrojový soubor, musíme ji umístit do samostatného souboru, označit ji jako public a soubor pojmenovat stejným jménem. Pokud to uděláme a třída se přeloží, můžeme ji používat v našem kódu.

Práce s proměnnými

Jak jsme se již dozvěděli, proměnné v jazyce Java mohou být buď některého základního typu, nebo typu odkazu na objekt. Pokud je proměnná typu reference na objekt, může být její hodnotou buď odkaz na objekt třídy, která je deklarovaná, nebo objekt třídy, která je potomkem deklarované třídy. Existuje jedna speciální hodnota referenční proměnné, která říká, že tato proměnná neukazuje na žádný objekt. Tato speciální hodnota se označuje slovem null a může být dosazena jako hodnota proměnné:

Zakaznik z = null;

Tímto příkazem jsme pouze deklarovali proměnnou typu Zakaznik, ale nepřiřadili jsme ji jako hodnotu žádný objekt. Hodnotu této proměnné můžeme určit později příkazem

z = new Zakaznik();

Pokud deklarujeme dvě proměnné kompatibilních typů, můžeme jejich hodnoty vzájemně přiřadit např. příkazem:

Zakaznik novak = z;

Po tomto příkazu budou obě dvě deklarované proměnné ukazovat na stejný objekt. To znamená, že změníme-li hodnotu některé vlastnosti u proměnné z, změní se samozřejmě i vlastnost u objektu, na který ukazuje proměnná novak -- jedná se totiž o tentýž objekt (vytvořený jedním voláním příkazu new Zakaznik()) -- viz obrázek Objekty v Javě.

Při deklarování proměnné se může, ale nemusí uvést její inicializační hodnota. Pokud tuto hodnotu neuvedeme, bude po deklaraci hodnota číselné proměnné 0, typu boolean false a referenční proměnné null. V definici jazyka Java jsou tyto hodnoty uvedeny, ale je otázkou slušného programátorského stylu všechny proměnné inicializovat, protože to podstatným způsobem ulehčí čtení programu.

Výrazy a příkazy

Zatím jsme se věnovali pouze tomu, jak nějakému objektu stanovit jeho vlastnosti, případně mu napsat nějakou metodu. To ovšem samozřejmě není vše, co s proměnnými a metodami můžeme dělat. Z proměnných, volání metod, závorek a operátorů můžeme sestavovat výrazy podobně jako v jazyce C. U každého operátoru je určeno, jaký typ musí mít jednotlivé operandy. V následující tabulce je přehled operátorů, které můžete použít v jazyce Java:

Operátory jazyka Java

OperátorArgumentyVýznam

=proměnná, výrazpřiřazení

+ - * / %číslo, číslosčítání, odčítání, násobení, dělení, modulo

--číslounární dekrement (prefix nebo postfix)

++číslounární inkrement (prefix nebo postfix)

||, &&boolean, booleanlogický OR, AND

& | ^ ~číslo, číslobitový AND, OR, XOR, NOT

==rovná se

!=nerovná se

!booleannegace

<= < >= >číslo, čísloje menší nebo rovno, je menší, je větší nebo rovno, je větší

<< >> >>>číslo, o kolikbitový posun doleva, doprava, doprava bez sign bitu

? :podmíněné vyhodnocení

Aritmetické operátory se chovají tak, jak bychom čekali -- sčítání sečte hodnotu svých argumentů apod. Zajímavější je to u operátorů přiřazení. Napíšeme-li x = y, provede se přiřazení hodnoty, která je v proměnné y do proměnné x a hodnota tohoto výrazu bude přiřazovaná hodnota. To, že přiřazení je obyčejný výraz, umožňuje použít následující syntaxi x = (y = z). Tedy nejprve se hodnota z proměnné z přiřadí do y a potom do x. Hodnota přiřazovacího výrazu má typ shodný s přiřazovaným typem. Pro porovnávání použijeme operátor ==, který porovná své argumenty a jehož výsledkem je hodnota typu boolean. Porovnávání pomocí == nebo pomocí != (nerovná se) budeme často používat při podmíněných příkazech a cyklech.

Pokud za výrazem uděláme středník, stává se z něj příkaz. Příkaz může být součástí bloku příkazů -- blok příkazů je uzavřen ve složených závorkách { }. Každé tělo metody tvoří jeden takovýto blok. Samozřejmě, že výrazy se mohou skládat nejen z operací prováděných s proměnnými (vlastnostmi), ale také s výsledky volání metod:

int vrat10() { return 10; }
int vrat15() { return 15; }
...
void fce() {
int k = vrat10() + vrat15();
....
}

V tomto příkladu se použije volání metod vrat10 a vrat15 ve funkci fce. Použití volání funkcí (metod) je tedy v jazyce Java stejné jako ve známých procedurálních jazycích (C, Pascal).

Všechny aritmetické a logické operátory se dají zkombinovat s přiřazením následujícím způsobem:

a += 10; // přičte 10 do proměnné a
a = a + 10; // stejný význam jako
// předchozí řádek
b *= 10; // vynásobí b 10

Složené přiřazení tedy nejprve provede uvedenou operaci a výsledek uloží zpět do první proměnné. Je to vlastně jenom zkratka sloužící k tomu, aby byl zápis často prováděných operací kratší. Tato syntaxe plně vychází z jazyka C.


| <<< | COMPUTERWORLD | IDG CZ homepage |