Realizace objektové teorie v Jave Java je plne objektový jazyk a témer vše, co v programech použijete, je trída nebo instance trídy. Výjimku tvorí primitivní datové typy - celá a desetinná císla, logická hodnota a znak. Ale i pro ne vJave existují jejich objektové protejšky. Dalším speciálním typem v Jave je rozhraní. Pri deklaraci trídy používá Java klícové slovo class. V rámci trídy mužeme nadefinovat datové atributy a metody. Datové atributy mohou být bud data trídy (tj. nezávislé na existenci konkrétní instance trídy) nebo data instance, stejne lze rozdelit metody. Metody a datové atributy trídy jsou rozlišeny pomocí modifikátoru static. Promenné a metody instancí nemají speciální modifikátor. Pokud jde o dedicnosti, je v Jave implementována jednonásobná dedicnost, tj. trída v Jave muže mít jen jednoho prímého predka. Celý strom dedicnosti vychází ze trídy Object, která jako jediná nemá žádného predka. Jako cástecnou náhradu vícenásobné dedicnosti využívá Java implementaci rozhraní. Rozhraní definuje metodu ci metody, které však neimplementuje. Trída, která implementuje rozhraní, musí všechny metody tohoto rozhraní naimplementovat. Jedna trída muže implementovat více rozhraní. Jedno rozhraní muže být implementováno v libovolném poctu tríd bez ohledu na dedicnou hierarchii. Pokud trída implementuje nejaké rozhraní, ríká tím, že realizuje dovednosti (metody) deklarované v tomto rozhraní. Pro rozhraní používá Java klícové slovo interface. Pred popisem realizace zapouzdrení v Jave je nutno nejdríve vysvetlit pojem package (balícek). Cílem balícku je omezit jmenné konflikty, tj. aby se výrazne omezila pravdepodobnost vzniku dvou tríd stejného jména s ruznými funkcemi. Balícky se organizují dle urcité funkcní príbuznosti a casto i dle jmen organizací, ve kterých vznikají. Napr. existuje balícek java.math, který obsahuje mnoho tríd implementujících matematické funkce ci balícek java.io, který obsahuje trídy pro práci se soubory (práce se soubory však není pouze vtomto balícku). Programátori pak mají také k dispozici mechanismus vytvárení vlastních balícku. Pro urcení míry prístupnosti (zapouzdrení) používá Java ctyri možnosti. Jestliže oznacíme datový atribut nebo metodu modifikátorem private, bude prístupná pouze ve tríde obsahující její definici. Promenná ci metoda deklarovaná jako private není dostupná ani potomkum této trídy. Promenná nebo metoda oznacená jako protected je dostupná pro všechny trídy ze stejného balíku a ve všech trídách vytvorených jako potomci této trídy, a to i v prípade, že trída potomka je z jiného balíku. Promenná nebo metoda oznacená jako public je dostupná z jakékoli trídy. Pokud není žádný z techto modifikátoru prístupu uveden, je k promenné/metode definován implicitní (prátelský) prístup – promenná/metoda je prístupná z jakékoliv trídy, která je soucástí stejného balíku. Je tedy rozdíl mezi deklarací s modifikátorem protected a bez modifikátoru. Promenné deklarované uvnitr metod jsou viditelné pouze v rámci této metody a nelze je nijak zverejnit. Další objektovou vlastnost polymorfismus Java umožnuje využít dvema zpusoby. Prvním z nich je pretežování (overloading) metod, tj. možnost v jedné tríde definovat nekolik metod stejného jména, které se liší poradím nebo typem nebo poctem parametru (pozor, nelze rozlišovat pouze typem návratové hodnoty). Další možností je tzv. prekrývání (overriding) metod v rámci dedicnosti. U potomka prekryjeme metodu predka, tj. vytvoríme metodu stejného jména a se stejným poctem, poradím a typy parametru a se stejnou návratovou hodnotou. Kód této metody však bude prizpusoben potrebám trídy potomka. Java spustí vždy metodu odpovídající dané tríde (tzv.pozdní vazba).
Konvence pro psaní programu v Jave Nyní si popíšeme konvence pro psaní programu v Jave založené na standardech firmy SUN. Jejich cílem je usnadnit ctení programu vytvorených jinými osobami a omezení nekterých chyb, které mohou vznikat z nepozornosti. Jména souboru Pro prípony souboru existují následující standardy: Java source .java Java bytecode .class Java archive .jar Soucástí každého adresáre by mel být soubor README ci index.html, ve kterém je popsán obsah adresáre. Struktura zdrojového souboru Javy Soubory by nemely mít více než 2000 rádek. Každý zdrojový soubor obsahuje jednu verejnou trídu/rozhraní. Verejná trída by mela predcházet privátní trídy a rozhraní v tomto souboru. Zdrojový soubor by se mel skládat z následujících cásti: • úvodní komentáre, které by mely obsahovat název souboru, historii zmen a prípadné autorské údaje, viz príklad: /* * File: Kniha.java * * 21 jan 2001 xabcd01 * 26 jan 2001 xabcd01 */
• •
prvni verze doplnena metoda vlozISBN
príkazy package a import vlastní trídy a rozhraní vcetne popisu jednotlivých tríd/rozhraní a metod. Následující tabulka obsahuje prehled jednotlivých cástí trídy/rozhraní v doporuceném poradí:
Cást deklarace trídy/rozhraní dokumentace k tríde/rozhraní (/** .... */) príkaz class nebo interface komentáre k implementaci trídy/rozhraní (pokud jsou potreba)
Poznámky popisy a komentáre, které prevezme Javadoc
promenné trídy (static)
v poradí public, protected, standardní (tj. bez modifikátoru prístupu) a nakonec private ve stejném poradí, jako promenné trídy
promenné instance konstruktory metody
komentáre k implementaci celé trídy, u kterých není vhodné, aby byly vdokumentaci generované pomocí Javadoc
metody by mely být serazeny dle funkcní príbuznosti, tak aby ctení bylo snazší
Tabulka 6.1: Prehled jednotlivých cástí trídy nebo rozhraní v doporuceném poradí Zarovnávání, odsazování Odsazovat by se melo po 4 mezerách. Jednotlivé rádky by nemely být delší než 80 znaku, nebot poté jsou obtížne zobrazované na mnoha terminálech ci v nekterých nástrojích. Rádky je vhodné rozdelovat za cárkou ci pred operátorem.
Komentáre Rozlišují se dva typy komentáru: • dokumentacní, které se zahrnují do dokumentace vytvárené programem Javadoc (/** .... */), • implementacní, které se podobají komentárum z C++ (/* ....*/ a //) Dokumentacní komentáre musí být uvedeny pred každou deklarací trídy, metody, rozhraní ci promenné, které mají modifikátor prístupu public ci protected. Zacínají znaky /** a koncí znaky */. Text muže obsahovat formátovací znaky HTML a speciální príznaky pro Javadoc: @author jméno-autora trídy @version císelné oznacení verze trídy @see odkaz na jinou trídu/metodu ci na nejaké URL @param jméno_parametru popis parametru @return popis návratové hodnoty z metody @exception jméno_výjimky popis výjimky, která muže nastat v metode
Následuje príklad dokumentacních komentáru: /** * Tato trida slouzi ke generovani vystupu s ruznymi zpusoby * kodovani ceskych znaku * *@author xabcd01 *@created 27. may 2001 */ public class Unicode { /** *obsahuje seznam ceskych znaku v kodovani Unicode v notaci Javy */ public static final String retezec = "\u00e1\u00c1\u00e9 ..."; /** * metoda main dle prvniho parametru prikazove radky vytiskne * retezec na standardni vystup v pozadovanem kodovani * *@param ARGS prvni parametr prikazove radky obsahuje retezec * specifikujici pozadovane kodovani *@see Supported Encoding v dokumentaci JDK */ public static void main(String ARGS[]) { ...
Príkaz javadoc Unicode.java vygeneruje dokumentaci ve formátu html. Implementacní komentáre slouží ke komentování implementace trídy a mely by se používat v prípade, kdy slouží ke lepšímu ctení ci porozumení kódu. Nemely by duplikovat informace, které lze snadno vycíst z vlastního kódu. Potreba psát implementacní komentáre je nekdy príznakem nízké kvality návrhu kódu – v tomto prípade je vhodnejší prepsat kód. Následují príklady implementacních komentáru:
/* * komentár, který je na více * rádcích */ /* jednorádkový komentár */ príkaz
// komentár, který koncí na konci rádku
Deklarace Na každém rádku by mela být uvedena deklarace pouze jedné promenné: int velikost;
// velikost souboru
Soucástí deklarace by mela být i inicializace s výjimkou prípadu, kdy pocátecní hodnota závisí na nejaké operaci. Deklarace by mely být na zacátku bloku (na zacátku metody, na zacátku trídy, ...) a ne až pred prvním použitím. Pro toto pravidlo existuje jedna výjimka – rídící promenná cyklu u príkazu for: for (int i = 0; i < maxLoops; i++) { ... }
Príkazy Na každém rádku by mel být pokud možno jen jeden príkaz. Následují doporucené zpusoby formátování jednotlivých jazykových konstrukcí: if (podmínka) { príkazy } else { príkazy } while (podmínka) { príkazy } try { príkazy; } catch (ExceptionClass e) { príkazy; } finally { príkazy; }
Mezery Prázdné rádky zvyšují citelnost kódu, pokud oddelují logické celky. Okolo operátoru (+ - * = ...) by mely být na obou stranách mezery. Mezery by mely být i pred a za závorkou.
Konvence pro pojmenovávání Typ identi- Pravidlo pro pojmenování Príklady fikátoru balík (package) jméno balíku by melo obsahovat na zacátku com.sun.eng jméno domény organizace v obráceném org.acm.sequin.pretty poradí (cz.vse, com.sun), další strukturování cz.vse.xabcd01.ukol2 záleží na vnitrních konvencích organizace, org.gjt.mm.mysql
jméno by melo obsahovat pouze malá písmena, trída (class)
rozhraní (interface) metoda promenná
konstanta
jméno trídy by melo být podstatné jméno (více podstatných a prídavných jmen) zacínající velkým písmenem obdobne jako u trídy
class Kniha; class Casopis; class VstupniSchranka; interface Publikace; interface Schranka; vratMax(); ulozDelku(); nactiVetu(); Date datumNarozeni; int pocetVet; float studijniPrumer; int i;
jméno metody by melo obsahovat sloveso, melo by zacínat malým písmenem, další slova v názvu by mela zacínat velkým písmenem jméno promenné by melo zacínat malým písmenem, pokud obsahuje více slov, tak další by mela zacínat velkým písmenem, jméno promenné by nemelo obsahovat znaky $ a _, jméno promenné by melo vyjadrovat obsah, nemely by se používat jednoznaková jména s výjimkou rídících promenných cyklu typu integer (promenné i, j, k, l, m, n) jména konstant by mela být složena z velkých static final int MIN_WIDTH = 4; písmen, pro oddelení slov je použito podtržítko _ static final int GET_THE_CPU = 1;
Tabulka 6.2: Konvence pro pojmenovávání Trída Object Trída Object je v Jave predkem všech tríd a jako jediná žádného predka nemá. Pokud pri deklaraci své trídy neuvedete slovo extends a jméno predka, bude jako predek vaší trídy automaticky dosazena trída Object. Trída Object neobsahuje žádné zvnejšku viditelné datové atributy, je zde však definováno nekolik metod, které je možno zdedit prípadne prekrýt. Metoda toString() Tato metoda by mela vrátit textovou reprezentaci objektu, která je snadno citelné pro cloveka. Prekladac ji napr. automaticky používá v metode System.out.println(Object x). Metoda println() v tomto prípade zjistí textovou reprezentaci objektu pomocí metody toString() a tu vytiskne. Standardní formát výpisu metody toString() trídy Object však není príliš vhodný (vypisuje se jméno trídy a hashcode), proto se doporucuje tuto metodu prekrýt, pokud ji chceme využívat napr. pro kontrolní výpisy. Metoda clone() Metoda clone() se používá pro vytvárení identické kopie jedné instance (pozor!, je potreba rozlišovat kopii instance od vytvorení další reference na stejný objekt, která se vytvárí prirazovacím príkazem). Po provedení metody clone() jsou obe instance, puvodní i nová, na sobe nezávislé. Použití metody clone() muže vyvolat výjimku CloneNotSupportedException.
Pokud chceme ve vlastní tríde klonování použít, je nutné metodu clone() predefinovat a navíc musí naše trída implementovat rozhraní Cloneable, jinak bude vždy vyhozena výjimka CloneNotSupportedException. Rozhraní Cloneable neosahuje žádnou metodu, není tedy treba implementovat jinou metodu než clone(). Metoda equals() Metoda equals() se používá v prípade, že chceme zjistit, zda se dva objekty rovnají. Ve tríde Object je napsána nejprísnejším možným zpusobem, tj. porovnávané objekty musí být totožné, tj. oba názvy musí odkazovat na stejnou instanci (na stejné místo v pameti). Tento zpusob porovnávání nám obvykle nevyhovuje a proto je vhodné metodu equals () prekrýt a porovnávat obsahy. Takto je predefinována napr. ve trídách String, Integer. Metoda hashCode() Metoda hashCode() vrací pro každou instanci císlo typu int, které je po celou dobu života této instance stejné. Využívá se pri práci s dynamickými datovými strukturami HashSet nebo HasTable. Obvykle ji není treba predefinovat (dokumentace Javy obsahuje pravidla pro její predefinování). Metoda getClass() Tato metoda nám za behu programu muže vrátit informace o objektu. Vrací je jako instanci trídy Class, která je potomkem trídy Object. Napríklad jsme schopni za behu zjistit jméno trídy. String jmenoTridy = Instance.getClass().getName()
Tato metoda je finální tj. ani ji nemužeme prekrýt. Metoda finalize() Metoda, kterou spouští Garbage Collector pri cištení pameti od neplatných objektu, tj. spustí se až poté, co na tento objekt již nevede žádný odkaz. Metody notify(), notifyAll(), wait() Tyto metody se vážou k použití více vláken (thread) v programu. Deklarace trídy v Jave Deklaraci trídy v Jave lze schématicky popsat následovne: [ package JménoBalíku; ] [ import Jméno.trídy, …; ] public [final | abstract] class jmeno [extends JménoRodicovskéTrídy] [implements JménoRozhraní …] { promenné trídy; promenné instancí; vnorené trídy; konstruktory; metody; } prípadné neverejné trídy;
Klauzule package slouží pro zarazení trídy do urcitého balíku (package). Pokud se tato klauzule neuvede, bude trída zarazena do tzv. nepojmenovaného balíku. Nepovinná klauzule import slouží pouze pro zjednodušení psaní odkazu na jednotlivé trídy. Napr. pokud chci ve své tríde vytvorit instanci trídy java.util.Random, mám dve možnosti, jak se na tuto trídu odkázat: použít celé jméno java.util.Random (prehlednejší, jednoznacné, nárocnejší na psaní) zadat na zacátku klauzuli import java.util.Random a poté používat jméno Random V klauzuli import lze zadat více tríd a lze též použít i hvezdicky. Napr. zápis import java.util.*; umožnuje zkrácene psát jména všech tríd zbalíku java.util. Používání hvezdicek však muže vytváret velmi nepríjemné a težko odhalitelné chyby v prípade, že existují duplicity ve jménech objektu v ruzných balících. Prekladac Javy automaticky pri prekladu doplní import trídy java.lang, tj. nemusíme pro používání prvku tohoto balíku uvádet príslušný import. Modifikátory tríd Pred klícovým slovem class lze použít klícová slova public, final a abstract, jejichž význam a zpusob použití si nyní vysvetlíme. Je-li trída definována jako public, muže se na ni odkazovat jakákoli jiná trída. Pokud klícové slovo public nepoužijeme, muže být trída zprístupnována pouze ostatními trídami zbalíku. V jednom zdrojovém souboru musí být práve jedna trída, která je definovaná jako public. Klícové slovo final oznacuje trídu od níž již nelze definovat potomky. Klícovým slovem abstract oznacujeme trídu, od které nelze vytváret žádné instance a která se používá jako predek pro další trídy (lze zde nadefinovat, co vše mají tyto trídy spolecné). Jedine abstraktní trída muže obsahovat abstraktní metody. Je možná pouze kombinace public abstract. Dedicnost a implementace rozhraní Pokud chceme novou trídu vytvorit jako potomka nekteré jiné trídy, uvedeme za jménem trídy klícové slovo extends následované jménem trídy predka (trída musí mít pouze jednoho predka, pokud není uveden, automaticky se doplní jako predek trída Object). Pokud chceme, aby naše trída implementovala rozhraní, použijeme klícové slovo implements následované jménem jednoho ci více rozhraní. Je možno uvést obojí tj. dedení i implementaci rozhraní. Príklad implementace tríd v Jave Pro vysvetlení nekterých konstrukcí použijeme následující postupne vytvárený príklad. Cílem je vytvorit trídy pro dvourozmerné tvary, které umejí pocítat obvody a obsahy techto tvaru. Pro všechny tvary použijeme jednoho spolecného predka trídu Tvar. Zjednodušený model podle UML je na obrázku 6.1.
Tvar
Kruh
Obdelnik
Kosodelnik
Obrázek 6.1: Príklad s tvary, základní návrh Tento model se dá samozrejme rozšírit o další druhy dvourozmerných tvaru. Protože náš príklad je velmi jednoduchý, budeme zatím všechny trídy zapisovat do jednoho souboru. Pro testování funkcnosti vytvoríme vždy public class Tvary, která bude obsahovat metodu main().Ostatní trídy budou tedy deklarovány jako neverejné tj. bez modifikátoru prístupu public. V kódu všech tríd nebudeme uvádet dokumentacní komentáre pro javadoc, protože by ztežovaly prehlednost a neúmerne prodlužovaly text. Implementacní komentáre také nebudou vetšinou uvádeny, protože vysvetlení ke kódu najdeme v textu. Nebudou zde objasnovány základy jazyka Java, které byly popsány v [UvodJava]. Deklarace trídy Tvar Nyní si tedy nadeklarujeme trídu Tvar a vytvoríme ji s plne zapouzdrenými atributy. Trída Tvar nemá v našem modelu nakresleného predka, ani v kódu nepoužijeme dedicnost. Podle pravidel Javy je predkem trídy Tvar trída Object. Trída bude mít jeden datový atribut rozmer1 typu int, deklarovaný jako private. Dále si vytvoríme jednoduchý konstruktor pro naplnení tohoto datového atributu. Na obrázku 6.2 vidíme model trídy Tvar v UML. Pak následuje kód. Tvar rozmer1 : int Tvar()
Obrázek 6.2: Príklad s tvary, trída Tvar, první verze class Tvar{ private int rozmer1; Tvar(int a){ rozmer1 = a; } }
Realizace zapouzdrení Jak už bylo receno trídu Tvar jsme vytvorili se zapouzdreným datovým atributem rozmer1. Tento datový atribut nebude prímo prístupný ani potomkum. Pro zprístupnení promenné se v Jave vytvárejí dve metody. Pro zprístupnení datového atributu pro ctení, vytváríme metodu getJmenoPromenne() a pro zprístupnení pro zápis metodu setJmenoPromenne(). Modifikátory prístupu u techto metod se mohou lišit, záleží na tom, jestli chceme zprístupnit atributy odkudkoli nebo jen v balícku. V naší tríde Tvar zprístupníme promenou rozmer1 pro ctení odkudkoli, pro zápis bude tato promenná neprístupná. Vytvoríme tedy metodu getRozmer1(). public int getRozmer1(){ return rozmer1; }
Realizace tríd Kruh a Obdelník, dedicnost v Jave Nyní nadeklarujeme trídy Obdelnik a Kruh jako potomky trídy Tvar. Do trídy Obdelnik pridáme datový atribut rozmer2 typu int opet jako private a odpovídající metodu getRozmer2() pro zprístupnení. V obou trídách vytvoríme také konstruktory. Tvorba konstruktoru je objasnena v následující podkapitole. Model vidíme na obrázku 6.3. Další metody, které vytvoríme budou metody pro výpocet obsahu a obvodu. Tvar rozmer1 : int Tvar() getRozmer1()
Kruh Kruh() obvod() obsah()
Obdelnik rozmer2 : int Obdelnik() getRozmer2() obvod() obsah()
Obrázek 6.3: Príklad s tvary, trídy Tvar, Obdelnik a Kruh, první verze
class Obdelnik extends Tvar{ private int rozmer2; Obdelnik(int a,int b){ super (rozmer1); this.rozmer2 = rozmer2; //kód konstruktoru je vysvetlen v následující kapitole } int getRozmer2() { return rozmer2; } double obsah (){ return (getRozmer1() * rozmer2); } int obvod (){ return ((getRozmer1()+rozmer2)*2); } }
Výpis 6.1: Trída Obdelnik, první verze class Kruh extends Tvar{ Kruh(int rozmer ){ super(rozmer); //kód konstruktoru je vysvetlen v následující kapitole } double obsah(){ return (Math.PI*getRozmer1()*getRozmer1()); } double obvod(){ return (2*Math.PI*getRozmer1()); } }
Výpis 6.2: Trída Kruh, první verze. Pretypování instancí tríd S dedicností souvisí i možnosti pretypovávání instancí tríd. Každá instance muže být pretypována na typ predka. Takovéto pretypování se oznacuje jako pretypování smerem nahoru. Znamená to tedy, že mužeme napsat: Obdelnik o = new Obdelnik(12,4); Tvar t = (Tvar) o; Object obj = (Object) o;
Pro vytvorení instance trídy lze použít jakýkoli konstruktor potomka, napr.: Tvar t1 = new Obdelnik(6,8);
Takto vytvorená instance trídy Tvar však nemuže používat metody, které jsou vytvoreny až ve tríde Obdelnik, tedy obvod() a obsah(). Lze ji ale pretypovat smerem dolu tj. k potomkovi a pak tyto metody použít, tj.: Obdelnik o1 = (Obdelnik) t1; int obvod = o1.obvod();
Kvuli pretypování není nutné vytváret novou instanci, obvod lze zjistit i takto: int obvod = ((Obdelnik) t1).obvod();
Pretypování smerem dolu je možno na úroven použitého konstruktoru. Proto bude predcházející kód preložen a proveden bez chyb. Pokud bychom instanci trídy Tvar t1 vytvorili pomocí konstruktoru trídy Tvar, preklad by probehl bez chyb, ale ze behu by byla vyhozena výjimka ClassCastException. Konstruktory Konstruktory jsou speciální metody urcené pro vytvárení instancí tríd. V Jave platí, že konstruktor se jmenuje stejne jako trída (je to tedy jediný druh metody, jejíž jméno zacíná velkým písmenem). Dalším specifikem konstruktoru je, že pri deklaraci neuvádíme typ návratové hodnoty. Pred konstruktorem mohou být uvedeny pouze modifikátory pro prístup. Konstruktor spouštíme vždy ve spojení s klícovým slovem new. Implicitní konstruktor Každá trída musí mít konstruktor. Aby nedocházelo k problémum, Java automaticky doplní implicitní konstruktor bez parametru ke každé tríde, pro kterou žádný konstruktor nevytvoríme. Pokud napíšeme vlastní konstruktor s parametry, implicitní konstruktor bez parametru už Java nevytvorí. Jestliže chceme, aby jeden zpretížených konstruktoru byl bez parametru, musíme ho napsat. Mechanismus tvorby konstruktoru, použití klícových slov super a this Pri vytvárení instance, tedy pri spuštení konstruktoru, JVM postupuje následovne: • spustí konstruktor predka trídy, od níž vytváríme instanci, pokud není uvedeno jinak je to konstruktor bez parametru, • postup spouštení konstruktoru se opakuje až ke tríde Object, • postupne se provedou príkazy všech techto spuštených konstruktoru, Pokud chceme v rámci vytvárení instance spustit jiný konstruktor predka než konstruktor bez parametru, musíme na prvním rádku kódu konstruktoru uvést klícové slovo super a parametry námi zvoleného konstruktoru predka. Pokud trída predka konstruktor bez parametru nemá, je tento postup nutný. V rámci jedné trídy je možné konstruktor pretížit. Pokud vytváríme pretížený konstruktor, který je rozšírením jiného konstruktoru téže trídy, lze jako první príkaz uvést klícové slovo this a parametry rozširovaného konstruktoru. Tento konstruktor se spustí, spustí konstruktor predka atd. Vše si objasníme na príklade. Vytvoríme konstruktory jednotlivých tríd. V konstruktoru priradíme vstupní hodnoty do promenných rozmer1 a rozmer2. Jakmile vytvoríme napr. konstruktor trídy Tvar s jedním parametrem, implicitní konstruktor bez parametru se nevytvorí tj. nelze napsat Tvar novyTvar = new Tvar( );
Do konstruktoru obou potomku pak jako první príkaz musíme zapsat super (a), protože konstruktor bez parametru neexistuje a naším cílem je predat hodnoty parametru do promenných instance. Konstruktory budou tedy vypadat následovne: Trída Tvar: Tvar(int a){ rozmer1 = a; }
Trída Obdelnik : Obdelnik(int a, int b){ super (a); rozmer2 = b; }
Trída Kruh: Kruh(int a ){ super(a); }
Místo jmen parametru a a b je vhodnejší použít stejná jména jako promenné, do kterých se jejich hodnoty prirazují. Pro rozlišení parametru metody a datové promenné trídy použijeme identifikátor this. Konstruktory tedy budou vypadat následovne: Trída Tvar: Tvar(int rozmer1){ this.rozmer1 = rozmer1; }
Trída Obdelnik : Obdelnik(int rozmer1, int rozmer2){ super (rozmer1); this.rozmer2 = rozmer2; }
Trída Kruh: Kruh(int rozmer1 ){ super(rozmer1); }
Ve tríde Obdelnik napíšeme ješte jeden konstruktor. Ukážeme si tak použití další možnosti pri tvorbe konstruktoru. Bude to konstruktor s jedním parametrem pro vytvorení ctverce. Tento konstruktor jako první príkaz použije spuštení jiného konstruktoru téže trídy. Pro spuštení jiného konstruktoru téže trídy používáme identifikátor this() a uvedeme parametry námi volaného konstruktoru. Tento príkaz se v konstruktoru muže vyskytnout pouze jednou a jen jako první príkaz konstruktoru. Vytvoríme-li konstruktor s jedním parametrem pro vytvorení ctverce, jeho prvním príkazem muže být this (rozmer,rozmer), tedy spuštení již dríve napsaného konstruktoru se dvema parametry.
Puvodní konstruktor se dvema parametry Obdelnik(int rozmer1, int rozmer2){ super (rozmer1); this.rozmer2 = rozmer2; }
Nový konstruktor s jedním parametrem Obdelnik(int rozmer){ this (rozmer, rozmer); }
Realizace polymorfismu Nyní náš príklad ješte rozšíríme. Vytvoríme trídu Kosodelnik. Tato trída bude potomkem trídy Obdelnik a zdedí její dva rozmery. Navíc bude mít trída Kosodelnik private datový atribut uhel, který udává velikost úhlu mezi stranou a a b v radiánech. Napíšeme konstruktor a metody getUhel() a obsah(). Metodu pro obvod psát nebudeme, obvod kosodélníka se pocítá stejne jako u obdélníka, metodu obvod() tedy mužeme zdedit. Model celého príkladu je na obrázku 6.4. Pri psaní metody obsah() však narazíme na problém. Zatím jsme uvažovali, že obsah obdélníka bude typu int, nebot je výsledkem násobení dvou celých císel. Obsah kosodélníka je však témer vždy desetinné císlo. Metoda obsah() ve tríde Kosodelnik musí tedy mít návratovou hodnotu typu double. Pokud metoda obsah() ve tríde Obdelnik má návratový typ int a ve tríde Kosodelnik typ double, metodu v potomkovi neprekrýváme, ale pretežujeme. Jde však o neplatný pokus o pretížení, protože metody by se lišily pouze návratovou hodnotou. Našim cílem je ale metodu obsah() prekrýt (obsah kosodélníka se nedá vypocítat také vzorcem pro obdélník). Protože obsah kosodélníka je nutne reálné císlo, zmeníme tedy návratovou hodnotu metody obsah() ve tríde Obdelnik také na double, aby bylo možno metodu prekrýt. V kódu se objeví zápis Obdelnik o3 = new Kosodelnik(2,5,Math.toRadians(30)); . Jak už bylo receno v podkapitole o pretypování, pro vytvorení instance je možno použít jakýkoli konstruktor potomka. Jde tu vlastne o skryté pretypování smerem nahoru. Byla vytvorena instance trídy Obdelnik, která má všechny datové atributy trídy Kosodelnik a je možné ji na instanci trídy Kosodelnik pretypovat. I pres to, že instance o3 je typu Obdelnik, však bude obvod i obsah spocítán podle vzorce pro kosodélník. Java používá mechanizmus takzvané pozdní vazby a použije vždy metody trídy, jejímž konstruktorem byla instance vytvorena, i když už došlo k pretypování. Další príklady související s polymorfizmem a mechanizmem pozdní vazby najdete i v následujících príkladech.
Tvar rozmer1 Tvar() getRozmer1()
Obdelnik rozmer2 Obdelnik() obsah() obvod() getRozmer2()
Kruh Kruh() obvod() obsah()
Kosodelnik uhel Kosodelnik() obsah() getUhel()
Obrázek 6.4: Príklad s tvary, model první verze rešení Kompletní rešení úlohy class Tvar
{
private int rozmer1; public Tvar(int rozmer1){ this.rozmer1 = rozmer1; } public int getRozmer1(){ return rozmer1; } } class Obdelnik extends Tvar{ private int rozmer2; public Obdelnik(int rozmer1, int rozmer2){ super (rozmer1); this.rozmer2 = rozmer2; } public Obdelnik(int rozmer){ this (rozmer,rozmer); }
Výpis 6.3/1: Príklad s tvary, první verze celkového rešení. public int getRozmer2(){ return rozmer2;
} public double obsah (){ return (this.getRozmer1() * rozmer2); } public double obvod (){ return ((getRozmer1()+rozmer2)*2); } } class Kruh extends Tvar{ public Kruh(int rozmer ){ super(rozmer); } public double obsah(){ return (Math.PI*getRozmer1()*getRozmer1()); } public double obvod(){ return (2*Math.PI*getRozmer1()); } } class Kosodelnik extends Obdelnik { private double uhel; public Kosodelnik (int rozmer1, int rozmer2, double uhel) { super (rozmer1,rozmer2); this.uhel = uhel; } public double getUhel() { return uhel; } public double obsah() { return getRozmer1() * getRozmer2() * Math.sin(uhel); } }
Výpis 6.3/2: Príklad s tvary, první verze celkového rešení.
public class Tvary1 { public static void main(String args[]){ Kruh k = new Kruh(5); Obdelnik o1 = new Obdelnik(2,5); Obdelnik o2 = new Obdelnik(5); Obdelnik o3 = new Kosodelnik(2,5,Math.toRadians(30)); System.out.println("Kruh k ma obsah "+k.obsah()+ " a obvod "+k.obvod()); System.out.println("Obdelnik o1 ma obsah "+o1.obsah()+ " a obvod "+o1.obvod()); System.out.println("Obdelnik o2 (ctverec) ma obsah "+o2.obsah()+" a obvod "+o2.obvod()); System.out.println("Obdelnik o3 (pretypovany kosodelnik) ma obsah "+o3.obsah()+" a obvod "+o3.obvod()); } }
Výpis 6.3/3: Príklad s tvary, první verze celkového rešení. Ošetrení neprípustných hodnot v konstruktoru Konstruktory, které jsme vytvorili v našem príklade, zatím nereší problémy s neprípustnými hodnotami. Je možno vytvorit kruh ci obdélník se zápornými rozmery, nebo kosodélník s úhlem menším než nula ci vetším než p (Math.PI). Pro ošetrení takovýchto hodnot použijeme standardní mechanismus výjimek. Pro záporný ci nulový rozmer a pro neprípustnou velikost úhlu si vytvoríme vlastní výjimky. Tato možnost není jediná, kterou je možno použít. Jiným rešením je místo neprípustných hodnot dosadit implicitní hodnoty (pokud je možno je k dané tríde urcit) a dále pracovat s temito hodnotami. Pro náš príklad není toto rešení vhodné (Jaká je implicitní hodnota polomeru kruhu?). Tvorba vlastních výjimek Vlastní výjimky tvoríme jako trídy jejichž predkem je trída Exception. Vetšinou vytvoríme dva konstruktory. Jeden bez parametru a druhý s parametrem typu String, který nám umožní vytvorit vlastní popis výjimky pri jejím vyhození. Naše trídy pro výjimky budou tedy vypadat následovne: class NeniKladneCisloException extends Exception { public NeniKladneCisloException() {} public NeniKladneCisloException(String zprava) { super(zprava); } } class NepripustnaVelikostUhluException extends Exception { public NepripustnaVelikostUhluException() {} public NepripustnaVelikostUhluException(String zprava) { super(zprava); } }
Vyhození vlastní výjimky Takto vytvorené výjimky pak použijeme v našich konstruktorech. Konstruktory budou výjimku predávat výše. Pri použití konstruktoru pak bude nutné výjimku odchytit (všechny výjimky mimo RuntimeException je nutno odchytit nebo predat výš). Pro vyhození výjimky používá Java klícové slovo throw a pro predání výjimky výš klícové slovo throws. Kód konstruktoru trídy Kosodelnik vcetne ošetrení neprípustných hodnot bude vypadat takto: public Kosodelnik (int rozmer1, int rozmer2, double uhel) throws NeniKladneCisloException,NepripustnaVelikostUhluException{ super (rozmer1,rozmer2); if (uhel <= 0) throw new NepripustnaVelikostUhluException("uhel ("+uhel+") musi byt vetsi nez 0"); if (uhel >= Math.PI) throw new NepripustnaVelikostUhluException("uhel ("+uhel+") musi byt mensi nez Math.PI"); this.uhel = uhel; }
Použití konstruktoru pak muže vypadat následovne: try { Kosodelnik k = new Kosodelnik(2,5,1,2); catch ( Exception e) { e.printStackTrace(); System.exit(0); /*pokud chceme ukoncit celý program, * pro návrat z metody použijeme return */ }; System.out.println("Kosodelnik ma obsah "+k.obsah()+" a obvod "+k.obvod());
Pro vetší prehlednost budeme i nadále používat verze bez kontroly neprípustných hodnot. Abstraktní trídy Smyslem abstraktních tríd je vyjádrit, co je v odvozených trídách spolecného. Abstraktní trídy vytváríme, kdykoli chceme se skupinou tríd pracovat prostrednictvím spolecného rozhraní a víme, že instance této trídy nebudeme potrebovat tvorit. Metody, které chceme aby potomci realizovali, deklarujeme v abstraktní tríde jako abstraktní. Abstraktní metoda je tvorena pouze hlavickou. U všech potomku abstraktní trídy se pak kontroluje, jestli byly všechny abstraktní metody implementovány. Abstraktní trída se od bežné trídy liší vtechto vecech: • nelze vytvorit instanci abstraktní trídy konstruktorem této trídy • pouze abstraktní trídy mohou obsahovat abstraktní metody Trídu Tvar v našem príklade je velmi vhodné definovat jako abstraktní.Tvorí jen spolecné rozhraní všech tvaru a nechceme vytváret její instance.V zadání príkladu jsme si rekli, že nám jde o výpocty obsahu a obvodu dvourozmerných tvaru. Prostrednictvím abstraktní trídy Tvar jsme schopni tuto schopnost (výpocet obsahu a obvodu) zajistit i u dalších prípadných potomku. Stací jen ve tríde Tvar deklarovat abstraktní metody obvod() a obsah(). Jejich realizace pak bude vyžadována ve všech neabstraktních potomcích této trídy. Jak bude náš príklad vypadat s abstraktní trídou vidíte v modelu na obrázku 6.5.
Tvar rozmer1 Tvar() getRozmer1() obvod() obsah()
Obdelnik rozmer2 Obdelnik() obsah() obvod() getRozmer2()
Kruh Kruh() obvod() obsah()
Kosodelnik uhel Kosodelnik() obsah() getUhel()
Obrázek 6.5: Príklad s tvary, verze s abstraktní trídou Tvar. V našich trídách to znamená ješte jednu zmenu. V minulém príklade byly metody pro výpocet obvodu obdélníka a kruhu nezávislé a proto bylo možné, aby vracely hodnoty ruzných typu. Nyní musíme metodu obvod() ve tríde Obdelnik upravit tak, aby vracela hodnotu ve formátu double (nelze upravit typ návratové hodnoty u kruhu na int). Jen tak trída Obdelnik splní podmínku, že implementuje abstraktní metody predka (metoda musí mít stejné jméno, stejný pocet, poradí i typy parametru a stejnou návratovou hodnotu). V metode main() trídy Tvary si ukážeme použití spolecného rozhraní trídy Tvar a využití polymorfizmu a mechanizmu pozdní vazby. Náhodne vygenerujeme 10 tvaru a uložíme je do pole typu Tvar. Pak pole projdeme a vypíšeme obsahy a obvody. Prekladac pouze overí, zda metody obvod() a obsah() existují ve tríde Tvar. To, že jsou tyto metody abstraktní, není problém, instance abstraktní trídy není možné vytváret jinak než konstruktory neabstraktních potomku, existence metody je tedy zajištena. Položky pole jsou typu Tvar, byly ale vytvoreny ruznými konstruktory potomku. Pro výpocty obsahu a obvodu budou dík mechanismu pozdní vazby použity prekrývající metody tríd použitých konstruktoru.
abstract class Tvar { private int rozmer1; public Tvar(int rozmer1){ super(); this.rozmer1 = rozmer1; } public int getRozmer1(){ return rozmer1; } abstract double obsah(); abstract double obvod(); } class Obdelnik extends Tvar{ private int rozmer2; public Obdelnik(int rozmer1, int rozmer2){ super (rozmer1); this.rozmer2 = rozmer2; } public Obdelnik(int rozmer){ this (rozmer,rozmer); } public int getRozmer2() { return rozmer2; } public double obsah (){ return (this.getRozmer1() * rozmer2); } public double obvod (){ return ((getRozmer1()+rozmer2)*2); } } class Kruh extends Tvar{ public Kruh(int rozmer ){ super(rozmer); } public double obsah(){ return (Math.PI*getRozmer1()*getRozmer1()); } public double obvod(){ return (2*Math.PI*getRozmer1()); } }
Výpis 6.4/1: Príklad s tvary, rešení s abstraktní trídou Tvar.
class Kosodelnik extends Obdelnik { private double uhel; public Kosodelnik (int rozmer1, int rozmer2, double uhel) { super (rozmer1,rozmer2); this.uhel = uhel; } public double getUhel() { return uhel; } public double obsah() { return getRozmer1() * getRozmer2() * Math.sin(uhel); } } public class Tvary3 { public static void main(String args[]){ Tvar [] pole = new Tvar [10]; /* Generujeme náhodne typy tvaru a vytváríme je s náhodnými * velikostmi prípustných hodnot. */ for (int i = 0; i<pole.length; i++) switch((int)(Math.random()*4)){ case 0 : pole[i]= new Kruh((int) (Math.random()*10+1));break; case 1 : pole[i]= new Obdelnik ((int)(Math.random()*10+1));break; case 2 : pole[i]= new Obdelnik ((int)(Math.random()*10+1), (int)(Math.random()*10+1));break; case 3 : pole[i]= new Kosodelnik ((int)(Math.random()*10+1), (int)(Math.random()*10+1), (Math.random()*Math.PI));break; default : ; }; for (int i = 0; i<pole.length; i++) System.out.println (pole[i].getClass().getName() +" ma obsah " + pole[i].obsah() + " a obvod " + pole[i].obvod()); } }
Výpis 6.4/2: Príklad s tvary, rešení s abstraktní trídou Tvar. Rozhraní Rozhraní umožnuje stanovit podobu trídy: názvy metod, jejich parametry, návratové hodnoty, bez jakékoli implementace. Soucástí rozhraní mohou být i datové složky, ale musejí být deklarovány jako statické a finální, tj. rozhraní muže deklarovat pouze konstantu trídy. Rozhraní vlastne ríká, jaké dovednosti budou mít všechny trídy, které naimplementují toto rozhraní. Rozhraní deklarujeme pomocí slova interface. Pred tímto klícovým slovem mužeme uvést i modifikátor public, pak musíme naše rozhraní deklarovat v souboru stejného jména s koncovkou java. Bez uvedení tohoto modifikátoru je prístup deklarován jako prátelský a
deklarace rozhraní muže být uvedena v jednom souboru s jinými rozhraními ci trídami stejného balíku. Všechny metody deklarované v rozhraní jsou považovány za public a to i když modifikátor neuvedeme. Použití modifikátoru protected nebo private není možné. Jestliže tedy implementujeme metodu rozhraní musí být ve tríde deklarována jako public.
<
> VlastnostiTvaru obvod() obsah()
Obdelnik rozmer2 rozmer1 Obdelnik() obsah() obvod() getRozmer2() getRozmer1()
Kruh polomer Kruh() obvod() obsah() getPolomer()
Kosodelnik uhel Kosodelnik() obsah() getUhel()
Obrázek 6.6: Príklad s tvary, rešení s rozhraním. Každá trída v Jave má pouze jednoho prímého predka, ale muže implementovat neomezený pocet rozhraní, proto je vždy výhodné pokud spolecné vlastnosti mužeme abstrahovat až na úroven rozhraní. V našem príklade s tvary jsme v minulém rešení deklarovali trídu Tvar jako abstraktní. Nyní se pokusíme jít v abstrakci ješte dál a zmenit ji na rozhraní. V prípade rozhraní však nemuže obsahovat datový atribut rozmer1 ani konstruktor. Vrozhraní Tvar zustanou pouze metody obvod() a obsah(). Název tvar je však zavádející a proto radeji použijeme oznacení VlastnostiTvaru. Oproti minulému rešení musíme upravit i trídy Obdelnik a Kruh. Model rešení s rozhraním je na obrázku 6.6. Toto rešení použijeme pro ilustraci práce s rozhraním. Optimální rešení našeho zadání je minulá varianta s abstraktní trídou. Vytvorení rozhraní je výhodné všude tam, kde metody v rozhraní obsažené reprezentují obecnejší vlastnosti. Takto vytvorené rozhraní má vysokou pravdepodobnost znovupoužití. Pri testování ve tríde Tvary4 využijeme další ze zajímavých vlastností objektu v Jave. Instance trídy implementující rozhraní lze pretypovat nejen smerem nahoru, ale i na typ implementovaného rozhraní. Instanci rozhraní lze tedy vytvorit pretypováním instance implementující trídy. Další možnost, jak vytvorit instanci rozhraní, je použít konstruktor
implementující trídy. Instance rozhraní vždy muže používat pouze metody v rozhraní deklarované. Proto si mužeme vytvorit pole prvku typu VlastnostiTvaru a vytváret jeho položky pomocí konstruktoru tríd Obdelnik, Kruh a Kosodelnik. Metody pro výpocet obsahu a obvodu mohou instance rozhraní spustit. Vždy bude použit kód metody obvod() a obsah() podle toho, jaký konstruktor byl pri tvorbe instance použit. Tudíž i pri použití rozhraní funguje v Jave polymorfismus a mechanismus pozdní vazby. Výslednou podobu kódu našeho príkladu vidíte na výpise 6.5. interface VlastnostiTvaru { double obsah(); double obvod(); } class Obdelnik implements VlastnostiTvaru { private int rozmer1; private int rozmer2; public Obdelnik(int rozmer1, int rozmer2){ this.rozmer1 = rozmer1; this.rozmer2 = rozmer2; } public Obdelnik(int rozmer){ this (rozmer,rozmer); } public int getRozmer1(){ return rozmer1; } public int getRozmer2() { return rozmer2; } public double obsah (){ return (rozmer1 * rozmer2); } public double obvod (){ return ((rozmer1+rozmer2)*2); } } class Kruh implements VlastnostiTvaru{ private int polomer; public Kruh(int polomer ){ this.polomer = polomer; } public int getPolomer() { return polomer; }
Výpis 6.5/1: Príklad s tvary, rešení pomocí vytvorení a implementace rozhraní.
public double obsah(){ return (Math.PI*polomer*polomer); } public double obvod(){ return (2*Math.PI*polomer); } } class Kosodelnik extends Obdelnik { private double uhel; public Kosodelnik (int rozmer1, int rozmer2, double uhel) { super (rozmer1,rozmer2); this.uhel = uhel; } public double getUhel() { return uhel; } public double obsah() { return getRozmer1() * getRozmer2() * Math.sin(uhel); } } public class Tvary4 { public static void main(String args[]){ VlastnostiTvaru [] pole = new VlastnostiTvaru [10]; /* Generujeme náhodne typy tvaru a vytváríme je s náhodnými * velikostmi prípustných hodnot. */ for (int i = 0; i<pole.length; i++) switch((int)(Math.random()*4)){ case 0 : pole[i]= new Kruh((int)(Math.random()*10+1));break; case 1 : pole[i]= new Obdelnik ((int)(Math.random()*10+1));break; case 2 : pole[i]= new Obdelnik ((int)(Math.random()*10+1), (int)(Math.random()*10+1));break; case 3 : pole[i]= new Kosodelnik ((int)(Math.random()*10+1), (int)(Math.random()*10+1), (Math.random()*Math.PI));break; default : ; }; for (int i = 0; i<pole.length; i++) System.out.println (pole[i].getClass().getName() + " ma obsah " + pole[i].obsah() + " a obvod " + pole[i].obvod()); } }
Výpis 6.5/2: Príklad s tvary, rešení pomocí vytvorení a implementace rozhraní. Rušení instancí a uvolnování pameti V objektové teorii jsme hovorili i o destruktorech a uvolnování pameti. Uvolnování pameti od nepotrebných objektu vyrešili tvurci Javy pomocí mechanizmu, který nazvali Garbage
Collector. V Jave žádné destruktory nevytváríme, pokud potrebujeme pred zrušením instance v pameti provést nejaké akce použijeme metodu finalize(). Tato metoda nesmí být pretížena a musí mít vždy hlavicku public void finalize().Posledním príkazem metody finalize() by melo být volání super.finalize(). Ve vetšine tríd metodu finalize() nevytváríme. Tuto metodu ale nespouštíme prostrednictvím kódu programu. Mechanizmus je následující. Jakýkoli javovský program je spušten jako nejméne dvouvláknový. Jedno vlákno je samotný program a druhé vlákno s nižší prioritou je Garbage Collector. Garbage Collector prochází pamet a hledá instance, na které už neexistuje odkaz. Jestliže najde neplatný objekt, spustí metodu finalize() a pri dalším pruchodu pametí tento objekt zruší. Tento zpusob práce s pametí je velmi bezpecný a efektivní, za tento komfort platí javovské programy nižší rychlostí provádení.
Realizace cásti návrhu cestovní kanceláre.
<>
Comparable compareTo()
Adresa
Osoba
typ uliceCislo mesto PSC stat
rodnecislo jmeno prijmeni adresa
Adresa() Adresa() getTyp() 1 getUliceCislo() getMesto() getPsc() getStat() toString() fromString() valid()
Osoba() Osoba() getRodneCislo() getJmeno() getPrijmeni() getAdresa() setPrijmeni() setAdresa() toString() fromString() equals() compareTo() valid()
Zakaznik jizCestoval cisloPasu Zakaznik() 0..* Zakaznik() getCisloPasu() getJizCestoval() setCisloPasu() setJizCestoval() toString() fromString() valid()
Zakaznici seznam getPocetZakazniku() trideni() vratIterator() vlozZakaznika() zrusSeznam() Zakaznici() getZakaznikaCislo() nactiZe Souboru() ulozDoSouboru()
CestovniKancelar ukladaniZakazniku() nacitaniZakazniku()
Obrázek 6.7: Realizacní model cásti cestovní kanceláre. V kapitole 3.3 jsme si ukázali jak navrhnout trídy pro projekt cestovní kanceláre. Nyní si ukážeme jak realizovat cást tohoto návrhu v Jave. Realizujeme trídy Adresa, Osoba, Zakaznik
a tu funkcní cást trídy CestovniKancelar, která souvisí s evidencí zákazníku.. Data budeme ukládat do textového souboru. Oproti puvodnímu modelu zredukujeme i násobnost agregace adresy v osobe. Každá osoba bude mít práve jednu adresu. Trídu, která je v návrhovém modelu oznacena jako SeznamZakazniku, v našem rešení pojmenujeme Zakaznici. Pri realizaci této trídy využijeme toho, že Java umožnuje vytváret promenné a metody trídy. Všechna data i metody deklarujeme jako static, nebudeme vytváret instanci trídy (ani to nepovolíme protože vytvoríme private konstruktor , který bude prázdný). Takto zajistíme, že v programu bude použit jen jeden seznam zákazníku. Také umožníme prístup k tomuto seznamu ze všech tríd tvorících uživatelské rozhraní. Podobne vytvoríme i trídu CestovniKancelar. Konecná realizacní podoba modelu tríd podle UML pro tyto trídy je na obrázku 6.7. Zapouzdrení Trídy Adresa, Osoba a Zakaznik naprogramujeme s datovými atributy oznacenými private a pro položky, které se budou moci i menit napíšeme metodu setJmenoPolozky(). Pro všechna data techto tríd napíšeme metody getJmenoPolozky() a tím je zprístupníme pro ctení. Pro trídu Adresa žádnou metodu set nenapíšeme, pri zmene adresy zákazníka vytvoríme novou instanci této trídy pomocí konstruktoru a nahradíme jí tu stávající. Ve tríde Osoba vytvoríme pouze metody setPrijmeni() a setAdresa(), nedovolíme tedy menit rodné císlo a krestní jméno zákazníka. Pro trídu Zakaznik napíšeme metody setCisloPasu() a setJizCestoval(). Realizace agregace adresy v osobe Trída Osoba v sobe agreguje jako svoji soucást adresu. Realizace této agregace je velmi jednoduchá, protože každá instance trídy Osoba v sobe zahrnuje práve jednu instanci trídy Adresa. Deklarace datových atributu trídy Osoba tedy bude následující: private private private private
String String String Adresa
rodneCislo; jmeno; prijmeni; adresa;
V konstruktoru trídy Osoba se pak spouští konstruktor trídy Adresa, aby byl tento datový atribut správne inicializován. Konstruktor trídy Osoba je následující: public Osoba(String typ, String uliceCislo, String mesto, String psc, String stat, String rodneCislo, String jmeno, String prijmeni) { this.rodneCislo = rodneCislo; this.jmeno = jmeno; this.prijmeni = prijmeni; this.adresa = new Adresa(typ, uliceCislo, mesto, psc, stat); }
Realizace ukládání do souboru a nacítání ze souboru. Všechny tyto trídy budou mít metody, které nám pomohou pri ukládání údaju o zákaznících do textového souboru. Budou to metody toString() a fromString(). Metoda toString() slouží pro vytvorení textového retezce ze všech datových atributu trídy, jednotlivé položky budou od sebe oddeleny stredníkem. Metoda toString() ve trídách Adresa a Osoba prekryjí metodu toString() predka tj. trídy Object. Takto pojmenovanou metodu použijeme nejen pro ukládání do souboru, ale i pro výpisy na konzole pri ladení techto tríd. Pri realizaci metody toString() ve tríde Zakaznik si ukážeme další použití klícového slova super, tentokrát pro vyvolání metody predka. Kód metody toString() ve tríde Zakaznik bude vypadat takto:
public String toString() { return (cisloPasu + ";" + jizCestoval + ";" + super.toString()); }
Výraz super.toString() vyvolá provádení metody toString() ze trídy predka tj. ze trídy Osoba, kde metoda vypadá takto: public String toString() { return rodneCislo +";"+ jmeno +";"+prijmeni+";"+adresa.toString(); }
V rámci provádení této metody se spouští provedení metody toString() ze trídy Adresa, která má následující kód: public String toString() { return (typ+";"+uliceCislo+";"+mesto+";"+psc+";"+stat+";"); }
Pri použití metody toString() pro jednotlivé instance trídy Zakaznik získáme textové retezce, které mužeme zapsat jako jednotlivé vety textového souboru. Pro opacný postup vytvoríme metody fromString(). Ze souboru budou precteny jednotlivé vety, tj. textové retezce, které obsahují všechny údaje o zákazníkovi oddelené od sebe stredníky. V okamžiku, kdy získáme retezec ze souboru, nemáme žádnou instanci zákazníka, kterou bychom mohli použít pro spuštení metody. Metoda fromString() musí být tedy definována jako metoda trídy s modifikátorem static. Úkolem této metody je rozdelit retezec na jednotlivé položky a vytvorit instanci trídy Zakaznik. Pokud použijeme soubor, který byl vytvoren programem za použití metody toString() trídy Zakaznik, struktura vety bude tokenizaci vyhovovat. Pri použití souboru se špatnou strukturou vety, neprobehne její rozdelení správne a bude vyhozena výjimka NoSuchElementException. Pro tento prípad napíšeme naše metody fromString() jako metody které tuto výjimku predávají výše a o její zachycení se postaráme až na úrovni, kde cteme se souboru (to bude ve tríde Zakaznici). Pomocí tokenizace, kde jako rozdelovací znak nastavíme stredník, rozdelíme retezec na cásti a spustíme konstruktor trídy Zakaznik se 7 parametry. Tento konstruktor vytvoríme jako druhý konstruktor trídy. V rámci kódu tohoto konstruktoru spustíme konstruktor predka se 4 parametry (je také vytvoren jako druhý konstruktor trídy Osoba). Tento konstruktor pak spouští metodu fromString() trídy Adresa. Jak vypadají tyto metody vidíme v následujícím výpise: //konstruktor ze trídy Zakaznik se 7 parametry public Zakaznik(String cisloPasu, boolean jizCestoval, String rodneCislo, String jmeno, String prijmeni, String adresa) { super(rodneCislo, jmeno, prijmeni, adresa); this.cisloPasu = cisloPasu; this.jizCestoval = jizCestoval; }
//metoda fromString trídy Zakaznik public static Osoba fromString(String s) throws NoSuchElementException { StringTokenizer st = new StringTokenizer(s, ";"); return new Zakaznik(st.nextToken(), Boolean.valueOf(st.nextToken()).booleanValue(), st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken("\n")); } //konstruktor ze trídy Osoba se 4 parametry public Osoba(String rodneCislo, String jmeno, String prijmeni, String adresa) { this.rodneCislo = rodneCislo; this.jmeno = jmeno; this.prijmeni = prijmeni; this.adresa = Adresa.fromString(adresa); } //metoda fromString() trídy Adresa public static Adresa fromString(String s) throws NoSuchElementException { StringTokenizer st = new StringTokenizer(s, ";"); return new Adresa(st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken()); }
Ošetrení neprípustných hodnot a overení existence povinných údaju. V minulém príklade s tvary jsme si ukázali jak lze tvorit vlastní výjimky a použít je pri vytvárení instancí tríd již v konstruktoru. V tomto príklade použijeme jiné rešení. Lze totiž predpokládat, že program bude velmi casto instance tríd vytváret nacítáním ze souboru, kam je již dríve uložil. Docházelo by tudíž neustále ke kontrolování již nekolikrát zkontrolovaných dat. Kontrola bude nutná pouze u zadávání nového zákazníka ci pri zmenách údaju o zákazníkovi. Proto vytvoríme metody valid() pro jednotlivé trídy, které nám reknou, zda je instance zákazníka v porádku nebo ne. Do seznamu zákazníku pak predáme pouze správná data. Pri vytvárení techto metod si musíme stanovit, které údaje o zákazníkovi jsou povinné, tj. které musí být vyplneny, a které jsou nepovinné. Ve tríde Adresa jsou povinné položky uliceCislo a mesto. Ve tríde Osoba je nutné vyplnit všechny údaje a ve tríde Zakaznik cisloPasu. Pokud není nekterá z techto položek vyplnena (obsahuje bud hodnotu null ne prázdný retezec), vrací metoda hodnotu typu int ruznou od nuly. Pokud není vyplnena nepovinná hodnota je doplnena bud prázdným retezcem (napr. u PSC) nebo implicitní hodnotou (napr. u státu "CR"). Všechny údaje jsou typu String, krome rodného císla nemá smysl provádet nejaké další kontroly. Pro rodné císlo napíšeme do metody valid() kontrolu správnosti. Kód metody valid() ze všech tríd je uveden v následujícím výpisu.
// metoda valid() trídy Adresa public int valid() { if ((typ == null) || (typ.trim().equals(""))) typ = "trvalá"; if ((uliceCislo == null) || (uliceCislo.trim().equals(""))) return 1; if ((mesto == null) || (mesto.trim().equals(""))) return 1; if ((psc == null) || (psc.trim().equals(""))) psc = " "; if ((stat == null) || (stat.trim().equals(""))) stat = "CR"; return 0; } //metoda valid() trídy Osoba public int valid() { String srok,smesic,sden = ""; int rok,mesic,den = 0; if ((jmeno == null) || (jmeno.trim().equals(""))) return 2; if ((prijmeni == null) || (prijmeni.trim().equals(""))) return 2; if ((rodneCislo == null) || (rodneCislo.trim().equals(""))) return 2; rodneCislo = rodneCislo.trim(); if ((rodneCislo.length() < 10)||(rodneCislo.length() > 11)) return 3; srok = rodneCislo.substring(0,2); smesic = rodneCislo.substring(2,4); sden = rodneCislo.substring(4,6); try { rok = Integer.parseInt(srok); mesic = Integer.parseInt(smesic); den = Integer.parseInt(sden); } catch (NumberFormatException e) { return 3; } if (! (((mesic >= 1) && (mesic <= 12)) || ((mesic >= 51) && (mesic <=62)))) return 3; if (!((den >= 1) & (den <= 31))) return 3; return adresa.valid(); } // metoda valid() trídy Zakaznik public int valid() { if ((cisloPasu == null) || (cisloPasu.trim().equals(""))) return 4; return super.valid(); }
Realizace jednoznacnosti zákazníka V rámci evidence zákazníku musíme zarucit jejich jednoznacnou ident ifikaci. Ta muže být zajištena pomocí rodného císla. Pokud tedy vkládá uživatel údaje o zákazníkovi, musíme zkontrolovat, zda zákazník se stejným rodným císlem není již uložen. Dve instance trídy Zakaznik se pro nás tedy rovnají, pokud se rovnají jejich rodná císla. Takto upravíme metodu equals(), její kód vidíte v následujícím výpise. public boolean equals(Object o) { if (o instanceof Osoba) return this.rodneCislo.equals(((Osoba) o).rodneCislo); else return false; }
Realizace podpory pro trídení V modelu na obrázku 6.7 jsme mimo jiné uvedli, že trída Osoba implementuje rozhraní Comparable. Každá trída, implementující toto standardní rozhraní, umožnuje porovnávání dvou instancí této trídy. Rozhraní Comparable obsahuje pouze jednu metodu a to metodu compareTo(). Tuto metodu napíšeme tak, aby nám pozdeji ve tríde Zakaznici umožnila trídit seznam zákazníku podle príjmení a jména. Kód metody je následující: public int compareTo(Object o) throws ClassCastException { if (o instanceof Osoba) { Osoba o2 = (Osoba) o; int porovnejPrimeni= this.prijmeni.compareTo(o2.prijmeni); if (porovnejPrimeni != 0) return porovnejPrimeni; else return (this.jmeno.compareTo(o2.jmeno)); } else throw new ClassCastException(); }
V rozhraní Comparable je metoda compareTo() deklarována s parametrem typu Object. Tato obecná deklarace umožnuje implementaci v jakékoli tríde. Nese s sebou však i problém. Je možné metodu spustit s parametrem jiného typu než Osoba nebo její potomci. Tuto možnost musíme vzít pri tvorbe metody v úvahu. Proto, pokud je parametr instanci trídy Osoba, provedeme porovnávání položek prijmeni a prípadne jmeno. Jestliže je parametr jiného typu vyhodí metoda výjimku ClassCastException. Pro porovnání príjmení a jmen použijeme metodu compareTo() ze trídy String. Metoda compareTo() vrací hodnotu typu int. • V prípade, že pri trídení nezáleží na poradí vrací 0 (tj. v našem príklade, když príjmení i jméno jsou stejné). • Záporné císlo v prípade, že instance, uvedena jako parametr, má být pred instancí, která metodu spustila. • Kladné císlo, když instance uvedená v parametru má být v razení za instancí vyvolávající metodu. Realizace agregace s násobností nula a více V prípade agregace adresy v osobe jsme jako jeden datový atribut ve tríde Osoba definovali instanci trídy Adresa. Bylo receno, že každá osoba má jednu adresu. V prípade seznamu
zákazníku platí, že zákazníku je predem neurcený pocet. Trída Zakaznici bude obsahovat jako datový atribut seznam zákazníku. Java nám poskytuje nekolik možných struktur pro uložení zákazníku do pameti. Prvním z nich je pole, ale tato struktura není príliš vyhovující. U pole musíme predem urcit jeho délku. Protože ale konecný pocet zákazníku neznáme, muže se nám lehce stát, že pole poddimenzujeme. Pokud se pokusíme vložit více zákazníku než kolik deklarujeme položek poli, nahlásí Java výjimku. Mnohem vhodnejší je v tomto prípade použití dynamické struktury. Java nám nabízí tyto struktury: ArrayList, LinkedList, HasSet a TreeSet. Nejvhodnejší v našem príklade je použít ArrayList. Je možné indexovat položky, trídit pomocí metody sort() trídy Collections a nemusíme predem deklarovat pocet položek. Podrobnejší popis všech techto struktur a popis práce s nimi je uveden v [UvodJava].Trídu Zakaznici vytvoríme s jedním datovým atributem, deklarovaným jako private static ArrayList. Vytvoríme private konstruktor , který bude prázdný. Takto zabezpecíme aby nebylo možno vytváret instance této trídy. Všechny metody budou deklarovány jako metody trídy. Takto zajistíme, že v programu bude použit jen jeden seznam zákazníku. Také umožníme prístup k tomuto seznamu ze všech tríd tvorících uživatelské rozhraní. Vytvoríme metodu getPocetZakazniku() pro zjištení poctu položek v seznamu, metodu getZakaznikaCislo() pro vrácení instance zákazníka uložené na zadané pozici v seznamu. Pro nactení dat ze souboru napíšeme metodu nactiZeSouboru () pro zápis obsahu seznamu do souboru metodu ulozDoSouboru(). Obe metody pro práci se souborem budou prípadné IOException vyhazovat výš. Dále budeme realizovat metody zrusSeznam(), vlozZakaznika(), trideni() a metodu vratIterator(), která vrací instanci rozhraní Iterator umožnující procházení seznamu. Kód metod je velmi jednoduchý. Celý kód trídy nalezneme ve výpisu 6.9. Realizace správy souboru s údaji o zákaznících. Údaje o zákaznících budeme ukládat do textového souboru ZAKAZNICI.TXT. Nactení ze souboru do pameti a opetovné uložení pri skoncení práce s programem bude realizovat trída CestovniKancelar. V této tríde vytvoríme nekolik konstant trídy typu int, pro vracení zprávy o prubehu ukládání nebo nacítání souboru. Všechny konstanty i metody budou deklarovány static stejne jako ve tríde Zakaznici. Celé programové rešení, tedy kód všech tríd, o kterých jsme zatím hovorili, uvádíme zde. import java.util.*; public class Adresa { private private private private private
String String String String String
typ; uliceCislo; mesto; psc; stat;
public Adresa(String typ, String uliceCislo, String mesto, String psc, String stat) { this.typ = typ; this.uliceCislo = uliceCislo; this.mesto = mesto; this.psc = psc; this.stat = stat; }
Výpis 6.6/1: Trída Adresa. public String getTyp() { return typ; }
public String getUliceCislo() { return uliceCislo; } public String getMesto() { return mesto; } public String getPsc() { return psc; } public String getStat() { return stat; } public String toString() { return (typ + ";" + uliceCislo + ";" + mesto + ";" + psc + ";" + stat + ";"); } public int valid() { if ((typ == null) || (typ.trim().equals(""))) typ = "trvalá"; if ((uliceCislo == null)||(uliceCislo.trim().equals(""))) return 1; if ((mesto == null) || (mesto.trim().equals(""))) return 1; if ((psc == null) || (psc.trim().equals(""))) psc = " "; if ((stat == null) || (stat.trim().equals(""))) stat = "CR"; return 0; } public static Adresa fromString(String s) throws NoSuchElementException { StringTokenizer st = new StringTokenizer(s, ";"); return new Adresa(st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken()); } }
Výpis 6.6/2: Trída Adresa. import java.util.*; public class Osoba implements Comparable { private String rodneCislo; private String jmeno; private String prijmeni; private Adresa adresa; public Osoba(String String String this.rodneCislo =
typ, String uliceCislo, String mesto, psc, String stat, String rodneCislo, jmeno, String prijmeni) { rodneCislo;
this.jmeno = jmeno; this.prijmeni = prijmeni; this.adresa = new Adresa(typ, uliceCislo, mesto, psc, stat); } public Osoba(String rodneCislo, String jmeno, String prijmeni, String adresa) { this.rodneCislo = rodneCislo; this.jmeno = jmeno; this.prijmeni = prijmeni; this.adresa = Adresa.fromString(adresa); } public String getRodneCislo() { return rodneCislo; } public String getJmeno() { return jmeno; } public String getPrijmeni() { return prijmeni; } public Adresa getAdresa() { return adresa; } public int compareTo(Object o) throws ClassCastException { if (o instanceof Osoba) { Osoba o2 = (Osoba) o; int porovnejPrimeni = this.prijmeni.compareTo(o2.prijmeni); if (porovnejPrimeni != 0) return porovnejPrimeni; else return (this.jmeno.compareTo(o2.jmeno)); } else throw new ClassCastException(); }
Výpis 6.7/1: Trída Osoba. public boolean equals(Object o) { if (o instanceof Osoba) { return this.rodneCislo.equals(((Osoba) o).rodneCislo); } else return false; } public String toString() { return rodneCislo + ";" + jmeno + ";" + prijmeni + ";" + adresa.toString(); }
public int valid() { String srok,smesic,sden = ""; int rok,mesic,den = 0; if ((jmeno == null) || (jmeno.trim().equals(""))) return 2; if ((prijmeni == null) || (prijmeni.trim().equals(""))) return 2; if ((rodneCislo == null) || (rodneCislo.trim().equals(""))) return 2; rodneCislo = rodneCislo.trim(); if ((rodneCislo.length() < 10)||(rodneCislo.length() > 11)) return 3; srok = rodneCislo.substring(0,2); smesic = rodneCislo.substring(2,4); sden = rodneCislo.substring(4,6); try { rok = Integer.parseInt(srok); mesic = Integer.parseInt(smesic); den = Integer.parseInt(sden); } catch (NumberFormatException e) { return 3; } if (! (((mesic >= 1) && (mesic <= 12)) || ((mesic >= 51) && (mesic <=62)))) return 3; if (!((den >= 1) & (den <= 31))) return 3; return adresa.valid(); } public static Osoba fromString(String s) throws NoSuchElementException { StringTokenizer st = new StringTokenizer(s, ";"); return new Osoba(st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken("\n")); } }
Výpis 6.7/2: Trída Osoba. import java.util.*; public class Zakaznik extends Osoba { private String cisloPasu; private boolean jizCestoval; public Zakaznik(String typ, String uliceCislo, String mesto, String psc, String stat, String rodneCislo, String prijmeni, String jmeno, String cisloPasu, boolean jizCestoval) { super(typ, uliceCislo, mesto, psc, stat, rodneCislo, jmeno, prijmeni); this.cisloPasu = cisloPasu; this.jizCestoval = jizCestoval; }
public Zakaznik(String cisloPasu, boolean jizCestoval, String rodneCislo, String jmeno, String prijmeni, String adresa) { super(rodneCislo, jmeno, prijmeni, adresa); this.cisloPasu = cisloPasu; this.jizCestoval = jizCestoval; } public String getCisloPasu() { return cisloPasu; } public boolean getCestoval() { return jizCestoval; } public String toString() { return (cisloPasu + ";" + jizCestoval + ";" +super.toString()); } public int valid() { if ((cisloPasu == null) || (cisloPasu.trim().equals(""))) return 4; return super.valid(); } public static Osoba fromString(String s) throws NoSuchElementException { StringTokenizer st = new StringTokenizer(s, ";"); return new Zakaznik(st.nextToken(), Boolean.valueOf(st.nextToken()).booleanValue(), st.nextToken(), st.nextToken(), st.nextToken(), st.nextToken("\n")); } }
Výpis 6.8: Trída Zakaznik. import java.util.*; import java.io.*; public class Zakaznici { private static ArrayList seznam = new ArrayList(); private Zakaznici() {} public static int getPocetZakazniku() { return seznam.size(); } public static Zakaznik getZakaznikaCislo(int i) { return (Zakaznik) seznam.get(i); } public static void nactiZeSouboru(String soubor) throws IOException { BufferedReader br = new BufferedReader(new FileReader(soubor)); String radek; while ((radek = br.readLine()) != null) {
seznam.add(Zakaznik.fromString(radek)); } br.close(); } public static void zrusSeznam (){ seznam.clear(); } public static void ulozDoSouboru(String soubor) throws IOException { PrintWriter pw = new PrintWriter(new BufferedWriter (new FileWriter(soubor))); Iterator it = seznam.iterator(); while (it.hasNext()) { Zakaznik z = (Zakaznik) it.next(); pw.println(z.toString()); } pw.close(); } public static void vlozZakaznika(Zakaznik z) { seznam.add(z); } public static Iterator vratIterator() { return seznam.iterator(); } public static void trideni() { Collections.sort(seznam); } }
Výpis 6.9: Trída Zakaznici. import java.io.*; import java.util.*; public class CestovniKancelar { public static final int SOUBOR_ZAKAZNICI_NENALEZEN = 1; public static final int SOUBOR_ZAKAZNICI_PORUSENA_STRUKTURA = 2; public static final int SOUBOR_ZAKAZNICI_NACTEN = 0; public static final int SOUBOR_ZAKAZNICI_ULOZEN = 0; public static final int SOUBOR_ZAKAZNICI_NELZE_ULOZIT = 1;
public static int nacitaniZakazniku (){ try { Zakaznici.nactiZeSouboru("Zakaznici.txt"); return SOUBOR_ZAKAZNICI_NACTEN; } catch (IOException e1 ) { return SOUBOR_ZAKAZNICI_NENALEZEN; } catch (NoSuchElementException e2){ return SOUBOR_ZAKAZNICI_PORUSENA_STRUKTURA; } } public static int ukladaniZakazniku() { try { Zakaznici.ulozDoSouboru("Zakaznici.txt"); return SOUBOR_ZAKAZNICI_ULOZEN; } catch (IOException e1){ return SOUBOR_ZAKAZNICI_NELZE_ULOZIT ; } } }
Výpis 6.10: Trída CestovniKancelar.
Uživatelské rozhraní
Adresa
Osoba
<>
Comparable
Zakaznici
Zakaznik CestovniKancelar
OknoZakaznici
OknoCestovniKancelar OknoZakaznikVstup
Obrazek 6.8: Zjednodušený model celé realizace cásti cestovní kanceláre vcetne tríd pro uživatelské rozhraní. Nyní máme vytvoreny všechny trídy, tvorící logickou funkcnost našeho príkladu. Naším dalším úkolem bude vytvorit uživatelské rozhraní. Navrhneme trídy, které budou tvorit uživatelské rozhraní. Práce se zákazníky by mela umožnovat tyto cinnosti: 1. Vložení údaju o novém zákazníkovi. 2. Editaci údaju o zákazníkovi. 3. Zrušení zákazníka. 4. Zobrazení údaju o zákazníkovi. 5. Prirazení objednávky zájezdu zákazníkovi. 6. Prehled všech zákazníku. V našem príklade si ukážeme realizaci pouze cásti techto funkcí. Popis tvorby uživatelského rozhraní najdete v [UvodJava]. Ukážeme si realizaci rozhraní pro prohlížení seznamu zákazníku a pro vládání nového zákazníka. Zjednodušený model celé naší realizace vidíme na obrázku 6.8. V modelu nejsou uvedeny datové atributy ani metody tríd. Plný model by byl rozsáhlejší než jedna strana A4 a tudíž méne prehledný. Pokud bychom chteli mít model úplný, musel by obsahovat i vnitrní trídy, použité pro realizaci obsluhy událostí. Na obrázku 6.9 vidíme jak by se dala zakreslit trída OknoZakaznik vcetne vnitrních tríd v ní deklarovaných.
OknoZakaznici
<>
DatovyModel
zrusZakaznikaTlacitko novyZakaznikTlacitko detailTlacitko objednavkaTlacitko editaceTlacitko stornoTlacitko tabulka panelTlacitek panelNavrat vybranyRadek vlastnik
nadpisy getColumnName() getRowCount() getColumnCount() getValueAt() <>
NovyZakaznik
OknoZakaznici()
<>
windowClosing()
actionPerformed()
<>
<>
Navrat
vyberRadku
actionPerformed()
valueChanged()
Obrazek 6.9: Model trídy OknoZakaznik vcetne vnitrních tríd Vytvoríme jednoduchou hlavní obrazovku cestovní kanceláre, na kterou umístíme pouze tlacítko pro vyvolání obrazovky pro práci se zákazníky a tlacítko na ukoncení aplikace. V rámci metod trídy OknoCestovniKancelar spustíme metody trídy CestovniKancelar ukladaniZakazniku() a nacitaniZakazniku(). Pro prípadné zobrazení chybových hlášení použijeme porovnání s konstantami trídy CestovniKancelar. Pro zobrazení chybových hlášení využijeme metodu showMessageDialog() trídy JOptionPane Ve tríde OknoCestovníKancelar, vytvoríme také metodu main() pro spuštení celého programu.Kód této metody je uveden ve výpisu 6.11. a výsledný vzhled na obrázku 6.10.
Obrázek 6.10: Cestovní kancelár – hlavní okno import javax.swing.*; import java.awt.event.*; import java.awt.*; public class OknoCestovniKancelar extends JFrame { JButton zakaznici = new JButton("Zákazníci"); JButton konec = new JButton("Konec");
Výpis 6.11/1: Trída OknoCestovnoKancelar.
class OtevreniZakazniku implements ActionListener { public void actionPerformed(ActionEvent e) { OknoZakaznici oz = new OknoZakaznici (OknoCestovniKancelar.this, false); oz.pack(); oz.setVisible(true); zakaznici.setEnabled(false); } }; class Ukonceni implements ActionListener { public void actionPerformed(ActionEvent e) { ukonceni(); } }; void ukonceni(){ int kod = CestovniKancelar.ukladaniZakazniku(); if (kod == CestovniKancelar.SOUBOR_ZAKAZNICI_NELZE_ULOZIT) JOptionPane.showMessageDialog(null, "Do souboru Zákazníci.txt nelze zapsat", "Chyba pri práci se souborem", JOptionPane.ERROR_MESSAGE); dispose(); System.exit(0); } void zpristupneniDat() { int kod = CestovniKancelar.nacitaniZakazniku(); if (kod == CestovniKancelar.SOUBOR_ZAKAZNICI_NENALEZEN) JOptionPane.showMessageDialog(null, "Soubor Zákazníci.txt nelze otevrít", "Chyba pri práci se souborem", JOptionPane.ERROR_MESSAGE); else if(kod== CestovniKancelar.SOUBOR_ZAKAZNICI_PORUSENA_STRUKTURA) JOptionPane.showMessageDialog(null, "Soubor Zákazníci.txt má porušenou strukturu", "Chyba pri práci se souborem", JOptionPane.ERROR_MESSAGE); } OknoCestovniKancelar(String titulek) { super(titulek); getContentPane().setLayout(new GridLayout(1, 2, 20, 20)); zakaznici.addActionListener(new OtevreniZakazniku()); konec.addActionListener(new Ukonceni()); getContentPane().add(zakaznici); getContentPane().add(konec); addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { ukonceni(); } }); zpristupneniDat(); }
Výpis 6.11/2: Trída OknoCestovnoKancelar.
public static void main(String[] args) { OknoCestovniKancelar ck = new OknoCestovniKancelar ("Cestovní kancelár"); ck.setLocation(300, 20); ck.setSize(300, 50); ck.setVisible(true); } }
Výpis 6.11/3: Trída OknoCestovnoKancelar. Pro prohlížení celého seznamu zákazníku vytvoríme samostatné okno. Bude popsáno ve tríde OknoZakaznici, která bude potomkem trídy JDialog. Do horní cásti okna vložíme panel s tlacítky, která nám umožní další práci svybraným zákazníkem. V dolní cásti umístíme tlacítko pro zavrení tohoto okna. Stred okna bude tvorit komponenta JTable vložená do komponenty JScrollPane, která v prípade potreby pridá k tabulce posuvníky. Použití této komponenty nebylo popsáno v [UvodJava], proto se jí budeme venovat v následující podkapitole. Pro umístení tlacítek použijeme panely se standardním rozložením FlowLayout, pro celé okno pak standardní nastavení pro trídu JDialog tj. BorderLayout.Výsledný vzhled vidíme na obrázku 6.11. Pro vkládání údaju o zákazníkovi vytvoríme trídu OknoZakaznikVstup,opet jako potomka trídy JDialog, která bude obsahovat komponenty JButton, JLabel, JComboBox a JTextField, vložené do panelu tj. instancí trídy JPanel. Pro jednotlivé panely byly nastaveny ruzní správci rozložení (layout). Na obrázku 6.12 je videt, které komponenty byly vloženy do jednotlivých panelu a jaké rozložení v nich bylo použito. Celé okno pak má standardní rozložení BorderLayout. Pro testování správnosti a úplnosti vložených údaju bude využita metoda valid() trídy Zakaznik. Na chybné vložení údaju bude uživatel upozornen pomocí metody trídy JOptionPane showMessageDialog(). Vzhled okna pro vkládání zákazníku mužeme videt na obrázku 6.13.
Obrázek 6.11: Okno pro práci se zákazníky.
panelSNadpisem rozložení FlowLayout
panelVstupu rozložení BoxLayout s parametrem X_AXIS
panelPoli rozložení BoxLayout s parametrem Y_AXIS
panelNapisu rozložení BoxLayout s parametrem Y_AXIS
panelTlacitek rozložení FlowLayout
Obrázek 6.12: Použití panelu pri tvorbe trídy OknoZakaznik
Obrázek 6.13: Okno pro vkládání nového zákazníka. JTable Knihovna Swing poskytuje pro zobrazení dat vtabulce trídu JTable. Tato trída je zbalícku javax.swing.table. Pro sledování událostí z tabulky je treba importovat i balícek javax.swing.event.Pro použití JTable musíme naimplementovat rozhraní TableModel nebo jednodušeji vytvorit potomka trídy AbstractTableModel. Takto vytvorený model ríká jaká data vstupují do tabulky a prípadne i urcujeme další vlastnosti jednotlivých bunek tabulky.
Pro náš príklad vytvoríme vnitrní trídu DatovýModel, která je potomkem trídy AbstractTableModel. Musíme urcit nadpisy jednotlivých sloupcu pomocí metody getColumnName(). String[] nadpisy = {"Rodné císlo", "Príjmení", "Jméno"}; public String getColumnName(int col) { return nadpisy[col]; }
Dále musíme implementovat metodu getRowCount(), která vrací pocet rádku v tabulce. Pocet rádku v našem prípade bude odpovídat poctu prvku v seznamu zákazníku. Pro zjištení poctu sloupcu musíme implementovat metodu getColumnCount(), která bude vracet konstantu 3. public int getRowCount() { return Zakaznici.getPocetZakazniku(); } public int getColumnCount() { return 3; }
Poslední metoda, kterou musíme naimplementovat, je metoda getValueAt(). Tato metoda urcuje obsah každé bunky tabulky. V našem príklade tedy musíme nejprve získat pro každý rádek položku odpovídajícího poradí v seznamu (ArrayList i JTable císlují od 0, tj. k prvnímu rádku s indexem 0 je promítnuta hodnota z ArrayListu seznam s indexem 0 atd.). Pro jednotlivé sloupce pak získáme rodné císlo, jméno a príjmení odpovídajícími metodami trídy Zakaznik. public Object getValueAt(int row, int col) { Zakaznik zak = Zakaznici.getZakaznikaCislo(row); switch (col) { case 0 : return(zak.getRodneCislo()); case 1 : return(zak.getPrijmeni()); case 2 : return(zak.getJmeno()); default : return null; } }
Tyto metody musíme v potomkovi trídy AbstractTableModel naimplementovat vždy, když chceme použít komponentu JTable. Tak vznikne tabulka, která je urcena pouze pro zobrazení položek. Trída JTable poskytuje mnohem více možností, napr. editování položek, možnost vložení komponenty comboBox do bunky. Podrobnejší informace mužete získat v[JavaDoc] nebo [Tutorial] firmy Sun. Dále musíme urcit,že uživatel má možnost vybrat vždy jen jeden rádek pomocí metody setSelectionMode(). Trída JTable implementuje rozhraní ListSelectionModel, jehož konstantu pro nastavení použijeme. tabulka.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
Abychom mohli reagovat na uživatelskou obsluhu, musíme získat instanci rozhraní ListSelectionModel pomocí metody getSelectionModel(). Pro tuto instanci pak nastavíme ovladac, který bude vracet index vybraného rádku.
ListSelectionModel modelVyberu = tabulka.getSelectionModel(); modelVyberu.addListSelectionListener(new VyberRadku());
Posledním krokem je vytvorení vnitrní trídy VyberRadku, která implementuje metodu valueChanged() rozhraní ListSelectionListener. V této metode zjistíme puvodce události a pak pomocí metod isSelectionEmpty() a getMinSelectionIndex() index vybraného rádku. Ten nastavíme do datového atributu vybranyRadek trídy OknoZakaznici. Tuto promennou bychom pak mohli použít pro zobrazení detailu vybrané položky, pro editaci vybrané položky atd. class VyberRadku implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { ListSelectionModel lsm = (ListSelectionModel)e.getSource(); if (!lsm.isSelectionEmpty()) { vybranyRadek = lsm.getMinSelectionIndex(); } } };
Kód tríd OknoZakaznici a OknoZakaznikVstup je uveden na výpisech 6.12 a 6.13. import import import import import import import
javax.swing.*; java.awt.event.*; java.awt.*; javax.swing.table.*; javax.swing.event.*; java.io.*; java.util.*;
public class OknoZakaznici extends JDialog { JButton zrusZakaznikaTlacitko = new JButton("Zruš zákazníka"); JButton novyZakaznikTlacitko = new JButton("Nový zákazník"); JButton detailTlacitko = new JButton("Zobraz detail"); JButton editaceTlacitko = new JButton("Editace zákazníka"); JButton objednavkaTlacitko = new JButton("Objednání zájezdu"); JButton stornoTlacitko = new JButton("Zrušit"); JPanel panelTlacitek = new JPanel(); JPanel panelNavrat = new JPanel(); JTable tabulka = new JTable (new DatovyModel()); OknoCestovniKancelar vlastnik; int vybranyRadek = -1; class NovyZakaznik implements ActionListener{ public void actionPerformed (ActionEvent e){ OknoZakaznikVstup obrz = new OknoZakaznikVstup (OknoZakaznici.this,true); obrz.setLocation(200,200); obrz.setVisible(true); } };
Výpis 6.12/1: Trída OknoZakaznici
class Navrat implements ActionListener { public void actionPerformed(ActionEvent e) { OknoZakaznici.this.vlastnik.zakaznici.setEnabled(true); dispose } }; class DatovyModel extends AbstractTableModel { String[] nadpisy = {"Rodné císlo", "Príjmení", "Jméno"}; public String getColumnName(int col) { return nadpisy[col]; } public int getRowCount() { return Zakaznici.getPocetZakazniku(); } public int getColumnCount() { return 3; } public Object getValueAt(int row, int col) { Zakaznik zak = Zakaznici.getZakaznikaCislo(row); switch (col) { case 0 : return(zak.getRodneCislo()); case 1 : return(zak.getPrijmeni()); case 2 : return(zak.getJmeno()); default : return null; } } }; class VyberRadku implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { ListSelectionModel lsm = (ListSelectionModel)e.getSource(); if (!lsm.isSelectionEmpty()) vybranyRadek = lsm.getMinSelectionIndex(); } }; public OknoZakaznici(OknoCestovniKancelar vlastnik, boolean modal){ super(vlastnik,modal); this.vlastnik = vlastnik; tabulka.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); ListSelectionModel rowSM = tabulka.getSelectionModel(); rowSM.addListSelectionListener(new VyberRadku()); getContentPane().setLayout(new BorderLayout()); panelTlacitek.add(novyZakaznikTlacitko); panelTlacitek.add(detailTlacitko); panelTlacitek.add(editaceTlacitko); panelTlacitek.add(objednavkaTlacitko); panelTlacitek.add(zrusZakaznikaTlacitko); panelNavrat.add(stornoTlacitko); getContentPane().add(panelTlacitek,BorderLayout.NORTH); getContentPane().add(new JScrollPane(tabulka)); getContentPane().add(panelNavrat,BorderLayout.SOUTH);
Výpis 6.12/2: Trída OknoZakaznici
novyZakaznikTlacitko.addActionListener(new NovyZakaznik()); stornoTlacitko.addActionListener(new Navrat()); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { OknoZakaznici.this.vlastnik.zakaznici.setEnabled(true); dispose(); } }); } }
Výpis 6.12/3: Trída OknoZakaznici import import import import import
javax.swing.*; java.awt.event.*; java.awt.*; java.util.*; java.io.*;
public class OknoZakaznikVstup extends JDialog { JTextField rcVstup = new JTextField(11); JTextField pasVstup = new JTextField(10); JTextField jmenoVstup = new JTextField(15); JTextField prijmeniVstup = new JTextField(25); String [] typyAdres = {"trvalá", "prechodná" }; JComboBox typVyber = new JComboBox(typyAdres); JTextField uliceVstup = new JTextField(25); JTextField cisloVstup = new JTextField(6); JTextField pscVstup = new JTextField(6); JTextField mestoVstup = new JTextField(20); JTextField statVstup = new JTextField("Ceská republika",10); JButton okTlacitko = new JButton ("OK"); JButton cancelTlacitko = new JButton("Zrušit"); JLabel nadpis = new JLabel ("Vkládání údaju o zákazníkovi"); JPanel panelNadpisu = new JPanel(); JPanel panelPoli = new JPanel(); JPanel panelVstupu = new JPanel(); JPanel panelCely = new JPanel(); JPanel panelSNadpisem = new JPanel(); JPanel panelTlacitek = new JPanel(); OknoZakaznici vlastnik; class Ruseni implements ActionListener { public void actionPerformed (ActionEvent e) { dispose(); } };
Výpis 6.13/1: Trída OknoZakaznikVstup
class VlozeniZakaznika implements ActionListener { public void actionPerformed (ActionEvent e) { boolean jizCestoval=false; Zakaznik vkladanyZakaznik =new Zakaznik (typyAdres [typVyber.getSelectedIndex()], uliceVstup.getText()+" "+cisloVstup.getText(), mestoVstup.getText(),pscVstup.getText(), statVstup.getText(),rcVstup.getText(), prijmeniVstup.getText(), jmenoVstup.getText(), pasVstup.getText(), jizCestoval); int kod = vkladanyZakaznik.valid(); if (kod==1) { JOptionPane.showMessageDialog (null,"Zakazník nemá vyplneny povinné položky adresy", "Zákazník je špatne", JOptionPane.ERROR_MESSAGE); uliceVstup.requestFocus(); return; }; if (kod==2) { JOptionPane.showMessageDialog (null,"Zakazník nemá vyplneny všechny povinné osobní údaje", "Zákazník je špatne", JOptionPane.ERROR_MESSAGE); jmenoVstup.requestFocus(); return; }; if (kod==3) { JOptionPane.showMessageDialog (null,"Špatne vyplnené rodné císlo", "Zákazník je špatne", JOptionPane.ERROR_MESSAGE); rcVstup.requestFocus(); return; }; if (kod==4) { JOptionPane.showMessageDialog (null,"Zakazník nemá vyplneny všechny povinné položky", "Zákazník je špatne", JOptionPane.ERROR_MESSAGE); pasVstup.requestFocus(); return; };
Výpis 6.13/2: Trída OknoZakaznikVstup
Iterator it = Zakaznici.vratIterator(); while (it.hasNext()) { Zakaznik z = (Zakaznik) it.next(); if (z.equals(vkladanyZakaznik)) { JOptionPane.showMessageDialog (null,"Zakazník s tímto rodným císlem již existuje", "Duplicitni zákazník", JOptionPane.ERROR_MESSAGE); return; }; }; Zakaznici.vlozZakaznika(vkladanyZakaznik); Zakaznici.trideni(); vlastnik.tabulka.revalidate(); vlastnik.tabulka.repaint(); OknoZakaznikVstup.this.dispose(); } }; KeyListener obsluhaEnteru = new KeyAdapter () { public void keyTyped (KeyEvent e) { if (e.getKeyChar()==KeyEvent.VK_ENTER) { Component vstup = (Component)e.getSource(); vstup.transferFocus(); } } }; KeyListener obsluhaEnteru2 = new KeyAdapter () { public void keyTyped (KeyEvent e) { if (e.getKeyChar()==KeyEvent.VK_ENTER) { uliceVstup.requestFocus(); } } }; OknoZakaznikVstup(OknoZakaznici vlastnik, boolean modal){ super(vlastnik,modal); this.vlastnik = vlastnik; jmenoVstup.addKeyListener(obsluhaEnteru); prijmeniVstup.addKeyListener(obsluhaEnteru); rcVstup.addKeyListener(obsluhaEnteru); pasVstup.addKeyListener(obsluhaEnteru2); uliceVstup.addKeyListener(obsluhaEnteru); cisloVstup.addKeyListener(obsluhaEnteru); mestoVstup.addKeyListener(obsluhaEnteru); pscVstup.addKeyListener(obsluhaEnteru); statVstup.addKeyListener(obsluhaEnteru); okTlacitko.addActionListener(new VlozeniZakaznika()); cancelTlacitko.addActionListener(new Ruseni());
Výpis 6.13/3: Trída OknoZakaznikVstup
panelNadpisu.setLayout(new BoxLayout (panelNadpisu, BoxLayout.Y_AXIS)); panelPoli.setLayout(new BoxLayout(panelPoli,BoxLayout.Y_AXIS)); addALabel("Jméno: ", panelNadpisu); addATextField(jmenoVstup, panelPoli); addALabel("Príjmení: ", panelNadpisu); addATextField(prijmeniVstup, panelPoli); addALabel("Rodné císlo: ", panelNadpisu); addATextField(rcVstup, panelPoli); addALabel("Císlo pasu:",panelNadpisu); addATextField(pasVstup, panelPoli); addALabel("Adresa: ", panelNadpisu); addALabel(" ", panelPoli); addALabel(" - typ adresy: ", panelNadpisu); typVyber.setAlignmentX(Component.LEFT_ALIGNMENT); typVyber.setMaximumSize(typVyber.getPreferredSize()); typVyber.setBackground(Color.white); panelPoli.add(typVyber); addALabel(" - ulice: ", panelNadpisu); addATextField(uliceVstup, panelPoli); addALabel(" - císlo popisné: ", panelNadpisu); addATextField(cisloVstup, panelPoli); addALabel(" - mesto: ", panelNadpisu); addATextField(mestoVstup, panelPoli); addALabel(" - PSC: ", panelNadpisu); addATextField(pscVstup, panelPoli); addALabel(" - stát: ", panelNadpisu); addATextField(statVstup,panelPoli); panelNadpisu.setBorder(BorderFactory.createEmptyBorder (10, 10, 10, 10)); panelPoli.setBorder(BorderFactory.createEmptyBorder (10, 10, 10, 10)); panelVstupu.setLayout(new BoxLayout (panelVstupu,BoxLayout.X_AXIS)); panelVstupu.add(panelNadpisu); panelVstupu.add(panelPoli); nadpis.setFont(new Font("Dialog",Font.BOLD,20)); panelSNadpisem.add(nadpis); panelTlacitek.add(okTlacitko); panelTlacitek.add(cancelTlacitko); panelCely.setLayout(new BorderLayout()); panelCely.add(panelTlacitek, BorderLayout.SOUTH); panelCely.add(panelVstupu, BorderLayout.CENTER); panelCely.add(panelSNadpisem, BorderLayout.NORTH); setContentPane(panelCely); pack(); }
Výpis 6.13/4: Trída OknoZakaznikVstup
private void addALabel(String text, Container container) { JLabel napis = new JLabel(text); napis.setAlignmentX(Component.LEFT_ALIGNMENT); napis.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); container.add(napis); } private void addATextField(JTextField pole, Container container) { pole.setAlignmentX(Component.LEFT_ALIGNMENT); pole.setMaximumSize(pole.getPreferredSize()); container.add(pole); } }
Výpis 6.13/5: Trída OknoZakaznikVstup